aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch2591
1 files changed, 2591 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch
new file mode 100644
index 0000000000..349aee0517
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayer.java.patch
@@ -0,0 +1,2591 @@
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -4,10 +4,14 @@
+ import com.mojang.authlib.GameProfile;
+ import com.mojang.datafixers.util.Either;
+ import com.mojang.logging.LogUtils;
++import com.mojang.serialization.DataResult;
+ import com.mojang.serialization.Dynamic;
+ import java.net.InetSocketAddress;
++import java.net.SocketAddress;
+ import java.util.Collection;
++import java.util.Iterator;
+ import java.util.List;
++import java.util.Objects;
+ import java.util.Optional;
+ import java.util.OptionalInt;
+ import java.util.Set;
+@@ -35,9 +39,9 @@
+ import net.minecraft.network.chat.CommonComponents;
+ import net.minecraft.network.chat.Component;
+ import net.minecraft.network.chat.HoverEvent;
++import net.minecraft.network.chat.MutableComponent;
+ import net.minecraft.network.chat.OutgoingChatMessage;
+ import net.minecraft.network.chat.RemoteChatSession;
+-import net.minecraft.network.chat.Style;
+ import net.minecraft.network.protocol.Packet;
+ import net.minecraft.network.protocol.game.ClientboundAnimatePacket;
+ import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
+@@ -91,8 +95,8 @@
+ import net.minecraft.util.RandomSource;
+ import net.minecraft.util.Unit;
+ import net.minecraft.world.Container;
+-import net.minecraft.world.InteractionHand;
+-import net.minecraft.world.MenuProvider;
++import net.minecraft.world.EnumHand;
++import net.minecraft.world.ITileInventory;
+ import net.minecraft.world.damagesource.DamageSource;
+ import net.minecraft.world.effect.MobEffectInstance;
+ import net.minecraft.world.effect.MobEffects;
+@@ -115,6 +119,7 @@
+ import net.minecraft.world.entity.projectile.AbstractArrow;
+ import net.minecraft.world.entity.vehicle.AbstractMinecart;
+ import net.minecraft.world.entity.vehicle.Boat;
++import net.minecraft.world.food.FoodData;
+ import net.minecraft.world.inventory.AbstractContainerMenu;
+ import net.minecraft.world.inventory.ContainerListener;
+ import net.minecraft.world.inventory.ContainerSynchronizer;
+@@ -128,6 +133,7 @@
+ import net.minecraft.world.item.ServerItemCooldowns;
+ import net.minecraft.world.item.WrittenBookItem;
+ import net.minecraft.world.item.crafting.RecipeHolder;
++import net.minecraft.world.item.enchantment.EnchantmentHelper;
+ import net.minecraft.world.item.trading.MerchantOffers;
+ import net.minecraft.world.level.ChunkPos;
+ import net.minecraft.world.level.GameRules;
+@@ -135,26 +141,53 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.biome.BiomeManager;
+ import net.minecraft.world.level.block.Blocks;
++import net.minecraft.world.level.block.ChestBlock;
+ import net.minecraft.world.level.block.HorizontalDirectionalBlock;
+ import net.minecraft.world.level.block.NetherPortalBlock;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+ import net.minecraft.world.level.block.entity.CommandBlockEntity;
+ import net.minecraft.world.level.block.entity.SignBlockEntity;
+-import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.block.state.IBlockData;
+ import net.minecraft.world.level.border.WorldBorder;
++import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.portal.PortalInfo;
+-import net.minecraft.world.level.storage.LevelData;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.scores.PlayerTeam;
+ import net.minecraft.world.scores.ScoreAccess;
+ import net.minecraft.world.scores.ScoreHolder;
++import org.slf4j.Logger;
++import net.minecraft.world.damagesource.CombatTracker;
++import net.minecraft.world.scores.Scoreboard;
+ import net.minecraft.world.scores.Team;
+ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
+-import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.WeatherType;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.CraftWorldBorder;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftDimensionUtil;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.entity.EntityExhaustionEvent;
++import org.bukkit.event.player.PlayerBedLeaveEvent;
++import org.bukkit.event.player.PlayerChangedMainHandEvent;
++import org.bukkit.event.player.PlayerChangedWorldEvent;
++import org.bukkit.event.player.PlayerLocaleChangeEvent;
++import org.bukkit.event.player.PlayerPortalEvent;
++import org.bukkit.event.player.PlayerSpawnChangeEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
++import org.bukkit.event.player.PlayerToggleSneakEvent;
++import org.bukkit.inventory.MainHand;
++// CraftBukkit end
+
+ public class ServerPlayer extends Player {
++
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32;
+ private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10;
+@@ -173,31 +206,31 @@
+ private float lastSentHealth = -1.0E8F;
+ private int lastSentFood = -99999999;
+ private boolean lastFoodSaturationZero = true;
+- private int lastSentExp = -99999999;
+- private int spawnInvulnerableTime = 60;
+- private ChatVisiblity chatVisibility = ChatVisiblity.FULL;
+- private boolean canChatColor = true;
+- private long lastActionTime = Util.getMillis();
++ public int lastSentExp = -99999999;
++ public int spawnInvulnerableTime = 60;
++ private ChatVisiblity chatVisibility;
++ private boolean canChatColor;
++ private long lastActionTime;
+ @Nullable
+ private Entity camera;
+- private boolean isChangingDimension;
++ public boolean isChangingDimension;
+ private boolean seenCredits;
+- private final ServerRecipeBook recipeBook = new ServerRecipeBook();
++ private final ServerRecipeBook recipeBook;
+ @Nullable
+ private Vec3 levitationStartPos;
+ private int levitationStartTime;
+ private boolean disconnected;
+- private int requestedViewDistance = 2;
+- private String language = "en_us";
++ private int requestedViewDistance;
++ public String language = "en_us"; // CraftBukkit - default
+ @Nullable
+ private Vec3 startingToFallPosition;
+ @Nullable
+ private Vec3 enteredNetherPosition;
+ @Nullable
+ private Vec3 enteredLavaOnVehiclePosition;
+- private SectionPos lastSectionPos = SectionPos.of(0, 0, 0);
+- private ChunkTrackingView chunkTrackingView = ChunkTrackingView.EMPTY;
+- private ResourceKey<Level> respawnDimension = Level.OVERWORLD;
++ private SectionPos lastSectionPos;
++ private ChunkTrackingView chunkTrackingView;
++ private ResourceKey<Level> respawnDimension;
+ @Nullable
+ private BlockPos respawnPosition;
+ private boolean respawnForced;
+@@ -205,109 +238,182 @@
+ private final TextFilter textFilter;
+ private boolean textFilteringEnabled;
+ private boolean allowsListing;
+- private WardenSpawnTracker wardenSpawnTracker = new WardenSpawnTracker(0, 0, 0);
+- private final ContainerSynchronizer containerSynchronizer = new ContainerSynchronizer() {
+- @Override
+- public void sendInitialData(AbstractContainerMenu container, NonNullList<ItemStack> items, ItemStack carriedItem, int[] initialData) {
+- ServerPlayer.this.connection
+- .send(new ClientboundContainerSetContentPacket(container.containerId, container.incrementStateId(), items, carriedItem));
++ private WardenSpawnTracker wardenSpawnTracker;
++ private final ContainerSynchronizer containerSynchronizer;
++ private final ContainerListener containerListener;
++ @Nullable
++ private RemoteChatSession chatSession;
++ private int containerCounter;
++ public boolean wonGame;
+
+- for (int i = 0; i < initialData.length; i++) {
+- this.broadcastDataValue(container, i, initialData[i]);
++ // CraftBukkit start
++ public String displayName;
++ public Component listName;
++ public org.bukkit.Location compassTarget;
++ public int newExp = 0;
++ public int newLevel = 0;
++ public int newTotalExp = 0;
++ public boolean keepLevel = false;
++ public double maxHealthCache;
++ public boolean joining = true;
++ public boolean sentListPacket = false;
++ public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
++ // CraftBukkit end
++
++ public ServerPlayer(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientinformation) {
++ super(worldserver, worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle(), gameprofile);
++ this.chatVisibility = ChatVisiblity.FULL;
++ this.canChatColor = true;
++ this.lastActionTime = Util.getMillis();
++ this.recipeBook = new ServerRecipeBook();
++ this.requestedViewDistance = 2;
++ this.language = "en_us";
++ this.lastSectionPos = SectionPos.of(0, 0, 0);
++ this.chunkTrackingView = ChunkTrackingView.EMPTY;
++ this.respawnDimension = Level.OVERWORLD;
++ this.wardenSpawnTracker = new WardenSpawnTracker(0, 0, 0);
++ this.containerSynchronizer = new ContainerSynchronizer() {
++ @Override
++ public void sendInitialData(AbstractContainerMenu container, NonNullList<ItemStack> items, ItemStack carriedItem, int[] initialData) {
++ ServerPlayer.this.connection.send(new ClientboundContainerSetContentPacket(container.containerId, container.incrementStateId(), items, carriedItem));
++
++ for (int i = 0; i < initialData.length; ++i) {
++ this.broadcastDataValue(container, i, initialData[i]);
++ }
++
+ }
+- }
+
+- @Override
+- public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) {
+- ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(container.containerId, container.incrementStateId(), slot, itemStack));
+- }
++ @Override
++ public void sendSlotChange(AbstractContainerMenu container, int slot, ItemStack itemStack) {
++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(container.containerId, container.incrementStateId(), slot, itemStack));
++ }
+
+- @Override
+- public void sendCarriedChange(AbstractContainerMenu containerMenu, ItemStack stack) {
+- ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(-1, containerMenu.incrementStateId(), -1, stack));
+- }
++ @Override
++ public void sendCarriedChange(AbstractContainerMenu containerMenu, ItemStack stack) {
++ ServerPlayer.this.connection.send(new ClientboundContainerSetSlotPacket(-1, containerMenu.incrementStateId(), -1, stack));
++ }
+
+- @Override
+- public void sendDataChange(AbstractContainerMenu container, int id, int value) {
+- this.broadcastDataValue(container, id, value);
+- }
++ @Override
++ public void sendDataChange(AbstractContainerMenu container, int id, int value) {
++ this.broadcastDataValue(container, id, value);
++ }
+
+- private void broadcastDataValue(AbstractContainerMenu container, int id, int value) {
+- ServerPlayer.this.connection.send(new ClientboundContainerSetDataPacket(container.containerId, id, value));
+- }
+- };
+- private final ContainerListener containerListener = new ContainerListener() {
+- @Override
+- public void slotChanged(AbstractContainerMenu containerToSend, int dataSlotIndex, ItemStack stack) {
+- Slot slot = containerToSend.getSlot(dataSlotIndex);
+- if (!(slot instanceof ResultSlot)) {
+- if (slot.container == ServerPlayer.this.getInventory()) {
+- CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
++ private void broadcastDataValue(AbstractContainerMenu container, int id, int value) {
++ ServerPlayer.this.connection.send(new ClientboundContainerSetDataPacket(container.containerId, id, value));
++ }
++ };
++ this.containerListener = new ContainerListener() {
++ @Override
++ public void slotChanged(AbstractContainerMenu containerToSend, int dataSlotIndex, ItemStack stack) {
++ Slot slot = containerToSend.getSlot(dataSlotIndex);
++
++ if (!(slot instanceof ResultSlot)) {
++ if (slot.container == ServerPlayer.this.getInventory()) {
++ CriteriaTriggers.INVENTORY_CHANGED.trigger(ServerPlayer.this, ServerPlayer.this.getInventory(), stack);
++ }
++
+ }
+ }
+- }
+
+- @Override
+- public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) {
++ @Override
++ public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) {}
++ };
++ this.textFilter = minecraftserver.createTextFilterForPlayer(this);
++ this.gameMode = minecraftserver.createGameModeForPlayer(this);
++ this.server = minecraftserver;
++ this.stats = minecraftserver.getPlayerList().getPlayerStats(this);
++ this.advancements = minecraftserver.getPlayerList().getPlayerAdvancements(this);
++ this.setMaxUpStep(1.0F);
++ this.fudgeSpawnLocation(worldserver);
++ this.updateOptions(clientinformation);
++
++ // CraftBukkit start
++ this.displayName = this.getScoreboardName();
++ this.bukkitPickUpLoot = true;
++ this.maxHealthCache = this.getMaxHealth();
++ }
++
++ // Yes, this doesn't match Vanilla, but it's the best we can do for now.
++ // If this is an issue, PRs are welcome
++ public final BlockPos getSpawnPoint(ServerLevel worldserver) {
++ BlockPos blockposition = worldserver.getSharedSpawnPos();
++
++ if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) {
++ int i = Math.max(0, this.server.getSpawnRadius(worldserver));
++ int j = Mth.floor(worldserver.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
++
++ if (j < i) {
++ i = j;
++ }
++
++ if (j <= 1) {
++ i = 1;
++ }
++
++ long k = (long) (i * 2 + 1);
++ long l = k * k;
++ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
++ int j1 = this.getCoprime(i1);
++ int k1 = RandomSource.create().nextInt(i1);
++
++ for (int l1 = 0; l1 < i1; ++l1) {
++ int i2 = (k1 + j1 * l1) % i1;
++ int j2 = i2 % (i * 2 + 1);
++ int k2 = i2 / (i * 2 + 1);
++ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
++
++ if (blockposition1 != null) {
++ return blockposition1;
++ }
++ }
+ }
+- };
+- @Nullable
+- private RemoteChatSession chatSession;
+- private int containerCounter;
+- public boolean wonGame;
+
+- public ServerPlayer(MinecraftServer minecraftServer, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) {
+- super(level, level.getSharedSpawnPos(), level.getSharedSpawnAngle(), gameProfile);
+- this.textFilter = minecraftServer.createTextFilterForPlayer(this);
+- this.gameMode = minecraftServer.createGameModeForPlayer(this);
+- this.server = minecraftServer;
+- this.stats = minecraftServer.getPlayerList().getPlayerStats(this);
+- this.advancements = minecraftServer.getPlayerList().getPlayerAdvancements(this);
+- this.setMaxUpStep(1.0F);
+- this.fudgeSpawnLocation(level);
+- this.updateOptions(clientInformation);
++ return blockposition;
+ }
++ // CraftBukkit end
+
+ private void fudgeSpawnLocation(ServerLevel level) {
+- BlockPos sharedSpawnPos = level.getSharedSpawnPos();
+- if (level.dimensionType().hasSkyLight() && level.getServer().getWorldData().getGameType() != GameType.ADVENTURE) {
+- int max = Math.max(0, this.server.getSpawnRadius(level));
+- int floor = Mth.floor(level.getWorldBorder().getDistanceToBorder((double)sharedSpawnPos.getX(), (double)sharedSpawnPos.getZ()));
+- if (floor < max) {
+- max = floor;
++ BlockPos blockposition = level.getSharedSpawnPos();
++
++ if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
++ int i = Math.max(0, this.server.getSpawnRadius(level));
++ int j = Mth.floor(level.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
++
++ if (j < i) {
++ i = j;
+ }
+
+- if (floor <= 1) {
+- max = 1;
++ if (j <= 1) {
++ i = 1;
+ }
+
+- long l = (long)(max * 2 + 1);
+- long l1 = l * l;
+- int i = l1 > 2147483647L ? Integer.MAX_VALUE : (int)l1;
+- int coprime = this.getCoprime(i);
+- int randomInt = RandomSource.create().nextInt(i);
++ long k = (long) (i * 2 + 1);
++ long l = k * k;
++ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
++ int j1 = this.getCoprime(i1);
++ int k1 = RandomSource.create().nextInt(i1);
+
+- for (int i1 = 0; i1 < i; i1++) {
+- int i2 = (randomInt + coprime * i1) % i;
+- int i3 = i2 % (max * 2 + 1);
+- int i4 = i2 / (max * 2 + 1);
+- BlockPos overworldRespawnPos = PlayerRespawnLogic.getOverworldRespawnPos(
+- level, sharedSpawnPos.getX() + i3 - max, sharedSpawnPos.getZ() + i4 - max
+- );
+- if (overworldRespawnPos != null) {
+- this.moveTo(overworldRespawnPos, 0.0F, 0.0F);
+- if (level.noCollision(this)) {
++ for (int l1 = 0; l1 < i1; ++l1) {
++ int i2 = (k1 + j1 * l1) % i1;
++ int j2 = i2 % (i * 2 + 1);
++ int k2 = i2 / (i * 2 + 1);
++ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(level, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
++
++ if (blockposition1 != null) {
++ this.moveTo(blockposition1, 0.0F, 0.0F);
++ if (level.noCollision((Entity) this)) {
+ break;
+ }
+ }
+ }
+ } else {
+- this.moveTo(sharedSpawnPos, 0.0F, 0.0F);
++ this.moveTo(blockposition, 0.0F, 0.0F);
+
+- while (!level.noCollision(this) && this.getY() < (double)(level.getMaxBuildHeight() - 1)) {
+- this.setPos(this.getX(), this.getY() + 1.0, this.getZ());
++ while (!level.noCollision((Entity) this) && this.getY() < (double) (level.getMaxBuildHeight() - 1)) {
++ this.setPos(this.getX(), this.getY() + 1.0D, this.getZ());
+ }
+ }
++
+ }
+
+ private int getCoprime(int spawnArea) {
+@@ -318,67 +424,101 @@
+ public void readAdditionalSaveData(CompoundTag compound) {
+ super.readAdditionalSaveData(compound);
+ if (compound.contains("warden_spawn_tracker", 10)) {
+- WardenSpawnTracker.CODEC
+- .parse(new Dynamic<>(NbtOps.INSTANCE, compound.get("warden_spawn_tracker")))
+- .resultOrPartial(LOGGER::error)
+- .ifPresent(wardenSpawnTracker -> this.wardenSpawnTracker = wardenSpawnTracker);
++ DataResult<WardenSpawnTracker> dataresult = WardenSpawnTracker.CODEC.parse(new Dynamic(NbtOps.INSTANCE, compound.get("warden_spawn_tracker"))); // CraftBukkit - decompile error
++ Logger logger = ServerPlayer.LOGGER;
++
++ Objects.requireNonNull(logger);
++ dataresult.resultOrPartial(logger::error).ifPresent((wardenspawntracker) -> {
++ this.wardenSpawnTracker = wardenspawntracker;
++ });
+ }
+
+ if (compound.contains("enteredNetherPosition", 10)) {
+- CompoundTag compound1 = compound.getCompound("enteredNetherPosition");
+- this.enteredNetherPosition = new Vec3(compound1.getDouble("x"), compound1.getDouble("y"), compound1.getDouble("z"));
++ CompoundTag nbttagcompound1 = compound.getCompound("enteredNetherPosition");
++
++ this.enteredNetherPosition = new Vec3(nbttagcompound1.getDouble("x"), nbttagcompound1.getDouble("y"), nbttagcompound1.getDouble("z"));
+ }
+
+ this.seenCredits = compound.getBoolean("seenCredits");
+ if (compound.contains("recipeBook", 10)) {
+ this.recipeBook.fromNbt(compound.getCompound("recipeBook"), this.server.getRecipeManager());
+ }
++ this.getBukkitEntity().readExtraData(compound); // CraftBukkit
+
+ if (this.isSleeping()) {
+ this.stopSleeping();
+ }
+
++ // CraftBukkit start
++ String spawnWorld = compound.getString("SpawnWorld");
++ CraftWorld oldWorld = (CraftWorld) Bukkit.getWorld(spawnWorld);
++ if (oldWorld != null) {
++ this.respawnDimension = oldWorld.getHandle().dimension();
++ }
++ // CraftBukkit end
++
+ if (compound.contains("SpawnX", 99) && compound.contains("SpawnY", 99) && compound.contains("SpawnZ", 99)) {
+ this.respawnPosition = new BlockPos(compound.getInt("SpawnX"), compound.getInt("SpawnY"), compound.getInt("SpawnZ"));
+ this.respawnForced = compound.getBoolean("SpawnForced");
+ this.respawnAngle = compound.getFloat("SpawnAngle");
+ if (compound.contains("SpawnDimension")) {
+- this.respawnDimension = Level.RESOURCE_KEY_CODEC
+- .parse(NbtOps.INSTANCE, compound.get("SpawnDimension"))
+- .resultOrPartial(LOGGER::error)
+- .orElse(Level.OVERWORLD);
++ DataResult<ResourceKey<Level>> dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, compound.get("SpawnDimension")); // CraftBukkit - decompile error
++ Logger logger1 = ServerPlayer.LOGGER;
++
++ Objects.requireNonNull(logger1);
++ this.respawnDimension = (ResourceKey) dataresult1.resultOrPartial(logger1::error).orElse(Level.OVERWORLD);
+ }
+ }
++
+ }
+
+ @Override
+ public void addAdditionalSaveData(CompoundTag compound) {
+ super.addAdditionalSaveData(compound);
+- WardenSpawnTracker.CODEC
+- .encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker)
+- .resultOrPartial(LOGGER::error)
+- .ifPresent(tag -> compound.put("warden_spawn_tracker", tag));
++ DataResult<Tag> dataresult = WardenSpawnTracker.CODEC.encodeStart(NbtOps.INSTANCE, this.wardenSpawnTracker); // CraftBukkit - decompile error
++ Logger logger = ServerPlayer.LOGGER;
++
++ Objects.requireNonNull(logger);
++ dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
++ compound.put("warden_spawn_tracker", nbtbase);
++ });
+ this.storeGameTypes(compound);
+ compound.putBoolean("seenCredits", this.seenCredits);
+ if (this.enteredNetherPosition != null) {
+- CompoundTag compoundTag = new CompoundTag();
+- compoundTag.putDouble("x", this.enteredNetherPosition.x);
+- compoundTag.putDouble("y", this.enteredNetherPosition.y);
+- compoundTag.putDouble("z", this.enteredNetherPosition.z);
+- compound.put("enteredNetherPosition", compoundTag);
++ CompoundTag nbttagcompound1 = new CompoundTag();
++
++ nbttagcompound1.putDouble("x", this.enteredNetherPosition.x);
++ nbttagcompound1.putDouble("y", this.enteredNetherPosition.y);
++ nbttagcompound1.putDouble("z", this.enteredNetherPosition.z);
++ compound.put("enteredNetherPosition", nbttagcompound1);
+ }
+
+- Entity rootVehicle = this.getRootVehicle();
+- Entity vehicle = this.getVehicle();
+- if (vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger()) {
+- CompoundTag compoundTag1 = new CompoundTag();
+- CompoundTag compoundTag2 = new CompoundTag();
+- rootVehicle.save(compoundTag2);
+- compoundTag1.putUUID("Attach", vehicle.getUUID());
+- compoundTag1.put("Entity", compoundTag2);
+- compound.put("RootVehicle", compoundTag1);
++ Entity entity = this.getRootVehicle();
++ Entity entity1 = this.getVehicle();
++
++ // CraftBukkit start - handle non-persistent vehicles
++ boolean persistVehicle = true;
++ if (entity1 != null) {
++ Entity vehicle;
++ for (vehicle = entity1; vehicle != null; vehicle = vehicle.getVehicle()) {
++ if (!vehicle.persist) {
++ persistVehicle = false;
++ break;
++ }
++ }
+ }
+
++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) {
++ // CraftBukkit end
++ CompoundTag nbttagcompound2 = new CompoundTag();
++ CompoundTag nbttagcompound3 = new CompoundTag();
++
++ entity.save(nbttagcompound3);
++ nbttagcompound2.putUUID("Attach", entity1.getUUID());
++ nbttagcompound2.put("Entity", nbttagcompound3);
++ compound.put("RootVehicle", nbttagcompound2);
++ }
++
+ compound.put("recipeBook", this.recipeBook.toNbt());
+ compound.putString("Dimension", this.level().dimension().location().toString());
+ if (this.respawnPosition != null) {
+@@ -387,17 +527,45 @@
+ compound.putInt("SpawnZ", this.respawnPosition.getZ());
+ compound.putBoolean("SpawnForced", this.respawnForced);
+ compound.putFloat("SpawnAngle", this.respawnAngle);
+- ResourceLocation.CODEC
+- .encodeStart(NbtOps.INSTANCE, this.respawnDimension.location())
+- .resultOrPartial(LOGGER::error)
+- .ifPresent(tag -> compound.put("SpawnDimension", tag));
++ dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.respawnDimension.location());
++ logger = ServerPlayer.LOGGER;
++ Objects.requireNonNull(logger);
++ dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
++ compound.put("SpawnDimension", nbtbase);
++ });
+ }
++ this.getBukkitEntity().setExtraData(compound); // CraftBukkit
++
+ }
+
++ // CraftBukkit start - World fallback code, either respawn location or global spawn
++ public void spawnIn(Level world) {
++ this.setLevel(world);
++ if (world == null) {
++ this.unsetRemoved();
++ Vec3 position = null;
++ if (this.respawnDimension != null) {
++ world = this.server.getLevel(this.respawnDimension);
++ if (world != null && this.getRespawnPosition() != null) {
++ position = Player.findRespawnPositionAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).orElse(null);
++ }
++ }
++ if (world == null || position == null) {
++ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
++ position = Vec3.atCenterOf(world.getSharedSpawnPos());
++ }
++ this.setLevel(world);
++ this.setPos(position);
++ }
++ this.gameMode.setLevel((ServerLevel) world);
++ }
++ // CraftBukkit end
++
+ public void setExperiencePoints(int experiencePoints) {
+- float f = (float)this.getXpNeededForNextLevel();
++ float f = (float) this.getXpNeededForNextLevel();
+ float f1 = (f - 1.0F) / f;
+- this.experienceProgress = Mth.clamp((float)experiencePoints / f, 0.0F, f1);
++
++ this.experienceProgress = Mth.clamp((float) experiencePoints / f, 0.0F, f1);
+ this.lastSentExp = -1;
+ }
+
+@@ -418,7 +586,7 @@
+ this.lastSentExp = -1;
+ }
+
+- private void initMenu(AbstractContainerMenu menu) {
++ public void initMenu(AbstractContainerMenu menu) {
+ menu.addSlotListener(this.containerListener);
+ menu.setSynchronizer(this.containerSynchronizer);
+ }
+@@ -440,7 +608,7 @@
+ }
+
+ @Override
+- protected void onInsideBlock(BlockState state) {
++ protected void onInsideBlock(IBlockData state) {
+ CriteriaTriggers.ENTER_BLOCK.trigger(this, state);
+ }
+
+@@ -451,11 +619,16 @@
+
+ @Override
+ public void tick() {
++ // CraftBukkit start
++ if (this.joining) {
++ this.joining = false;
++ }
++ // CraftBukkit end
+ this.gameMode.tick();
+ this.wardenSpawnTracker.tick();
+- this.spawnInvulnerableTime--;
++ --this.spawnInvulnerableTime;
+ if (this.invulnerableTime > 0) {
+- this.invulnerableTime--;
++ --this.invulnerableTime;
+ }
+
+ this.containerMenu.broadcastChanges();
+@@ -464,10 +637,11 @@
+ this.containerMenu = this.inventoryMenu;
+ }
+
+- Entity camera = this.getCamera();
+- if (camera != this) {
+- if (camera.isAlive()) {
+- this.absMoveTo(camera.getX(), camera.getY(), camera.getZ(), camera.getYRot(), camera.getXRot());
++ Entity entity = this.getCamera();
++
++ if (entity != this) {
++ if (entity.isAlive()) {
++ this.absMoveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
+ this.serverLevel().getChunkSource().move(this);
+ if (this.wantsToStopRiding()) {
+ this.setCamera(this);
+@@ -493,20 +667,20 @@
+ super.tick();
+ }
+
+- for (int i = 0; i < this.getInventory().getContainerSize(); i++) {
+- ItemStack item = this.getInventory().getItem(i);
+- if (item.getItem().isComplex()) {
+- Packet<?> updatePacket = ((ComplexItem)item.getItem()).getUpdatePacket(item, this.level(), this);
+- if (updatePacket != null) {
+- this.connection.send(updatePacket);
++ for (int i = 0; i < this.getInventory().getContainerSize(); ++i) {
++ ItemStack itemstack = this.getInventory().getItem(i);
++
++ if (itemstack.getItem().isComplex()) {
++ Packet<?> packet = ((ComplexItem) itemstack.getItem()).getUpdatePacket(itemstack, this.level(), this);
++
++ if (packet != null) {
++ this.connection.send(packet);
+ }
+ }
+ }
+
+- if (this.getHealth() != this.lastSentHealth
+- || this.lastSentFood != this.foodData.getFoodLevel()
+- || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
+- this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
++ if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
++ this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit
+ this.lastSentHealth = this.getHealth();
+ this.lastSentFood = this.foodData.getFoodLevel();
+ this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F;
+@@ -519,27 +693,33 @@
+
+ if (this.foodData.getFoodLevel() != this.lastRecordedFoodLevel) {
+ this.lastRecordedFoodLevel = this.foodData.getFoodLevel();
+- this.updateScoreForCriteria(ObjectiveCriteria.FOOD, Mth.ceil((float)this.lastRecordedFoodLevel));
++ this.updateScoreForCriteria(ObjectiveCriteria.FOOD, Mth.ceil((float) this.lastRecordedFoodLevel));
+ }
+
+ if (this.getAirSupply() != this.lastRecordedAirLevel) {
+ this.lastRecordedAirLevel = this.getAirSupply();
+- this.updateScoreForCriteria(ObjectiveCriteria.AIR, Mth.ceil((float)this.lastRecordedAirLevel));
++ this.updateScoreForCriteria(ObjectiveCriteria.AIR, Mth.ceil((float) this.lastRecordedAirLevel));
+ }
+
+ if (this.getArmorValue() != this.lastRecordedArmor) {
+ this.lastRecordedArmor = this.getArmorValue();
+- this.updateScoreForCriteria(ObjectiveCriteria.ARMOR, Mth.ceil((float)this.lastRecordedArmor));
++ this.updateScoreForCriteria(ObjectiveCriteria.ARMOR, Mth.ceil((float) this.lastRecordedArmor));
+ }
+
+ if (this.totalExperience != this.lastRecordedExperience) {
+ this.lastRecordedExperience = this.totalExperience;
+- this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float)this.lastRecordedExperience));
++ this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float) this.lastRecordedExperience));
+ }
+
++ // CraftBukkit start - Force max health updates
++ if (this.maxHealthCache != this.getMaxHealth()) {
++ this.getBukkitEntity().updateScaledHealth();
++ }
++ // CraftBukkit end
++
+ if (this.experienceLevel != this.lastRecordedLevel) {
+ this.lastRecordedLevel = this.experienceLevel;
+- this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float)this.lastRecordedLevel));
++ this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float) this.lastRecordedLevel));
+ }
+
+ if (this.totalExperience != this.lastSentExp) {
+@@ -550,11 +730,27 @@
+ if (this.tickCount % 20 == 0) {
+ CriteriaTriggers.LOCATION.trigger(this);
+ }
+- } catch (Throwable var4) {
+- CrashReport crashReport = CrashReport.forThrowable(var4, "Ticking player");
+- CrashReportCategory crashReportCategory = crashReport.addCategory("Player being ticked");
+- this.fillCrashReportCategory(crashReportCategory);
+- throw new ReportedException(crashReport);
++
++ // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border
++ if (this.oldLevel == -1) {
++ this.oldLevel = this.experienceLevel;
++ }
++
++ if (this.oldLevel != this.experienceLevel) {
++ CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel);
++ this.oldLevel = this.experienceLevel;
++ }
++
++ if (this.getBukkitEntity().hasClientWorldBorder()) {
++ ((CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick();
++ }
++ // CraftBukkit end
++ } catch (Throwable throwable) {
++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking player");
++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Player being ticked");
++
++ this.fillCrashReportCategory(crashreportsystemdetails);
++ throw new ReportedException(crashreport);
+ }
+ }
+
+@@ -572,6 +768,7 @@
+ if (this.fallDistance > 0.0F && this.startingToFallPosition == null) {
+ this.startingToFallPosition = this.position();
+ }
++
+ }
+
+ public void trackEnteredOrExitedLavaOnVehicle() {
+@@ -586,42 +783,83 @@
+ if (this.enteredLavaOnVehiclePosition != null && (this.getVehicle() == null || !this.getVehicle().isInLava())) {
+ this.enteredLavaOnVehiclePosition = null;
+ }
++
+ }
+
+ private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) {
+- this.getScoreboard().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
++ // CraftBukkit - Use our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, (scoreaccess) -> {
++ scoreaccess.set(points);
++ });
+ }
+
+ @Override
+ public void die(DamageSource cause) {
+ this.gameEvent(GameEvent.ENTITY_DIE);
+- boolean _boolean = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
+- if (_boolean) {
+- Component deathMessage = this.getCombatTracker().getDeathMessage();
+- this.connection
+- .send(
+- new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage),
+- PacketSendListener.exceptionallySend(
+- () -> {
+- int i = 256;
+- String string = deathMessage.getString(256);
+- Component component = Component.translatable(
+- "death.attack.message_too_long", Component.literal(string).withStyle(ChatFormatting.YELLOW)
+- );
+- Component component1 = Component.translatable("death.attack.even_more_magic", this.getDisplayName())
+- .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, component)));
+- return new ClientboundPlayerCombatKillPacket(this.getId(), component1);
+- }
+- )
+- );
+- Team team = this.getTeam();
+- if (team == null || team.getDeathMessageVisibility() == Team.Visibility.ALWAYS) {
+- this.server.getPlayerList().broadcastSystemMessage(deathMessage, false);
+- } else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
+- this.server.getPlayerList().broadcastSystemToTeam(this, deathMessage);
+- } else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OWN_TEAM) {
+- this.server.getPlayerList().broadcastSystemToAllExceptTeam(this, deathMessage);
++ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
++ // CraftBukkit start - fire PlayerDeathEvent
++ if (this.isRemoved()) {
++ return;
++ }
++ java.util.List<org.bukkit.inventory.ItemStack> loot = new java.util.ArrayList<org.bukkit.inventory.ItemStack>(this.getInventory().getContainerSize());
++ boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
++
++ if (!keepInventory) {
++ for (ItemStack item : this.getInventory().getContents()) {
++ if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) {
++ loot.add(CraftItemStack.asCraftMirror(item));
++ }
+ }
++ }
++ // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule)
++ this.dropFromLootTable(cause, this.lastHurtByPlayerTime > 0);
++ for (org.bukkit.inventory.ItemStack item : this.drops) {
++ loot.add(item);
++ }
++ this.drops.clear(); // SPIGOT-5188: make sure to clear
++
++ Component defaultMessage = this.getCombatTracker().getDeathMessage();
++
++ String deathmessage = defaultMessage.getString();
++ keepLevel = keepInventory; // SPIGOT-2222: pre-set keepLevel
++ org.bukkit.event.entity.PlayerDeathEvent event = CraftEventFactory.callPlayerDeathEvent(this, loot, deathmessage, keepInventory);
++
++ // SPIGOT-943 - only call if they have an inventory open
++ if (this.containerMenu != this.inventoryMenu) {
++ this.closeContainer();
++ }
++
++ String deathMessage = event.getDeathMessage();
++
++ if (deathMessage != null && deathMessage.length() > 0 && flag) { // TODO: allow plugins to override?
++ Component ichatbasecomponent;
++ if (deathMessage.equals(deathmessage)) {
++ ichatbasecomponent = this.getCombatTracker().getDeathMessage();
++ } else {
++ ichatbasecomponent = org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(deathMessage);
++ }
++
++ this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), ichatbasecomponent), PacketSendListener.exceptionallySend(() -> {
++ boolean flag1 = true;
++ String s = ichatbasecomponent.getString(256);
++ MutableComponent ichatmutablecomponent = Component.translatable("death.attack.message_too_long", Component.literal(s).withStyle(ChatFormatting.YELLOW));
++ MutableComponent ichatmutablecomponent1 = Component.translatable("death.attack.even_more_magic", this.getDisplayName()).withStyle((chatmodifier) -> {
++ return chatmodifier.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, ichatmutablecomponent));
++ });
++
++ return new ClientboundPlayerCombatKillPacket(this.getId(), ichatmutablecomponent1);
++ }));
++ PlayerTeam scoreboardteam = this.getTeam();
++
++ if (scoreboardteam != null && scoreboardteam.getDeathMessageVisibility() != Team.Visibility.ALWAYS) {
++ if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
++ this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent);
++ } else if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OWN_TEAM) {
++ this.server.getPlayerList().broadcastSystemToAllExceptTeam(this, ichatbasecomponent);
++ }
++ } else {
++ this.server.getPlayerList().broadcastSystemMessage(ichatbasecomponent, false);
++ }
+ } else {
+ this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), CommonComponents.EMPTY));
+ }
+@@ -630,20 +868,27 @@
+ if (this.level().getGameRules().getBoolean(GameRules.RULE_FORGIVE_DEAD_PLAYERS)) {
+ this.tellNeutralMobsThatIDied();
+ }
+-
+- if (!this.isSpectator()) {
+- this.dropAllDeathLoot(cause);
++ // SPIGOT-5478 must be called manually now
++ this.dropExperience();
++ // we clean the player's inventory after the EntityDeathEvent is called so plugins can get the exact state of the inventory.
++ if (!event.getKeepInventory()) {
++ this.getInventory().clearContent();
+ }
+
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
+- LivingEntity killCredit = this.getKillCredit();
+- if (killCredit != null) {
+- this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType()));
+- killCredit.awardKillScore(this, this.deathScore, cause);
+- this.createWitherRose(killCredit);
++ this.setCamera(this); // Remove spectated target
++ // CraftBukkit end
++
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
++ LivingEntity entityliving = this.getKillCredit();
++
++ if (entityliving != null) {
++ this.awardStat(Stats.ENTITY_KILLED_BY.get(entityliving.getType()));
++ entityliving.awardKillScore(this, this.deathScore, cause);
++ this.createWitherRose(entityliving);
+ }
+
+- this.level().broadcastEntityEvent(this, (byte)3);
++ this.level().broadcastEntityEvent(this, (byte) 3);
+ this.awardStat(Stats.DEATHS);
+ this.resetStat(Stats.CUSTOM.get(Stats.TIME_SINCE_DEATH));
+ this.resetStat(Stats.CUSTOM.get(Stats.TIME_SINCE_REST));
+@@ -655,12 +900,13 @@
+ }
+
+ private void tellNeutralMobsThatIDied() {
+- AABB aABB = new AABB(this.blockPosition()).inflate(32.0, 10.0, 32.0);
+- this.level()
+- .getEntitiesOfClass(Mob.class, aABB, EntitySelector.NO_SPECTATORS)
+- .stream()
+- .filter(mob -> mob instanceof NeutralMob)
+- .forEach(mob -> ((NeutralMob)mob).playerDied(this));
++ AABB axisalignedbb = (new AABB(this.blockPosition())).inflate(32.0D, 10.0D, 32.0D);
++
++ this.level().getEntitiesOfClass(Mob.class, axisalignedbb, EntitySelector.NO_SPECTATORS).stream().filter((entityinsentient) -> {
++ return entityinsentient instanceof NeutralMob;
++ }).forEach((entityinsentient) -> {
++ ((NeutralMob) entityinsentient).playerDied(this);
++ });
+ }
+
+ @Override
+@@ -668,10 +914,12 @@
+ if (killed != this) {
+ super.awardKillScore(killed, scoreValue, damageSource);
+ this.increaseScore(scoreValue);
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
+ if (killed instanceof Player) {
+ this.awardStat(Stats.PLAYER_KILLS);
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
+ } else {
+ this.awardStat(Stats.MOB_KILLS);
+ }
+@@ -682,14 +930,18 @@
+ }
+ }
+
+- private void handleTeamKill(ScoreHolder scoreHolder, ScoreHolder scoreHolder1, ObjectiveCriteria[] objectiveCriterias) {
+- PlayerTeam playersTeam = this.getScoreboard().getPlayersTeam(scoreHolder1.getScoreboardName());
+- if (playersTeam != null) {
+- int id = playersTeam.getColor().getId();
+- if (id >= 0 && id < objectiveCriterias.length) {
+- this.getScoreboard().forAllObjectives(objectiveCriterias[id], scoreHolder, ScoreAccess::increment);
++ private void handleTeamKill(ScoreHolder scoreholder, ScoreHolder scoreholder1, ObjectiveCriteria[] aiscoreboardcriteria) {
++ PlayerTeam scoreboardteam = this.getScoreboard().getPlayersTeam(scoreholder1.getScoreboardName());
++
++ if (scoreboardteam != null) {
++ int i = scoreboardteam.getColor().getId();
++
++ if (i >= 0 && i < aiscoreboardcriteria.length) {
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(aiscoreboardcriteria[i], scoreholder, ScoreAccess::increment);
+ }
+ }
++
+ }
+
+ @Override
+@@ -698,16 +950,31 @@
+ return false;
+ } else {
+ boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && source.is(DamageTypeTags.IS_FALL);
++
+ if (!flag && this.spawnInvulnerableTime > 0 && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
+ return false;
+ } else {
+ Entity entity = source.getEntity();
+- if (entity instanceof Player player && !this.canHarmPlayer(player)) {
+- return false;
++
++ if (entity instanceof Player) {
++ Player entityhuman = (Player) entity;
++
++ if (!this.canHarmPlayer(entityhuman)) {
++ return false;
++ }
+ }
+
+- if (entity instanceof AbstractArrow abstractArrow && abstractArrow.getOwner() instanceof Player player1 && !this.canHarmPlayer(player1)) {
+- return false;
++ if (entity instanceof AbstractArrow) {
++ AbstractArrow entityarrow = (AbstractArrow) entity;
++ Entity entity1 = entityarrow.getOwner();
++
++ if (entity1 instanceof Player) {
++ Player entityhuman1 = (Player) entity1;
++
++ if (!this.canHarmPlayer(entityhuman1)) {
++ return false;
++ }
++ }
+ }
+
+ return super.hurt(source, amount);
+@@ -717,32 +984,46 @@
+
+ @Override
+ public boolean canHarmPlayer(Player other) {
+- return this.isPvpAllowed() && super.canHarmPlayer(other);
++ return !this.isPvpAllowed() ? false : super.canHarmPlayer(other);
+ }
+
+ private boolean isPvpAllowed() {
+- return this.server.isPvpAllowed();
++ // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
++ return this.level().pvpMode;
+ }
+
+ @Nullable
+ @Override
+ protected PortalInfo findDimensionEntryPoint(ServerLevel destination) {
+- PortalInfo portalInfo = super.findDimensionEntryPoint(destination);
+- if (portalInfo != null && this.level().dimension() == Level.OVERWORLD && destination.dimension() == Level.END) {
+- Vec3 vec3 = portalInfo.pos.add(0.0, -1.0, 0.0);
+- return new PortalInfo(vec3, Vec3.ZERO, 90.0F, 0.0F);
++ PortalInfo shapedetectorshape = super.findDimensionEntryPoint(destination);
++ destination = (shapedetectorshape == null) ? destination : shapedetectorshape.world; // CraftBukkit
++
++ if (shapedetectorshape != null && this.level().getTypeKey() == LevelStem.OVERWORLD && destination != null && destination.getTypeKey() == LevelStem.END) { // CraftBukkit
++ Vec3 vec3d = shapedetectorshape.pos.add(0.0D, -1.0D, 0.0D);
++
++ return new PortalInfo(vec3d, Vec3.ZERO, 90.0F, 0.0F, destination, shapedetectorshape.portalEventInfo); // CraftBukkit
+ } else {
+- return portalInfo;
++ return shapedetectorshape;
+ }
+ }
+
+ @Nullable
+ @Override
+ public Entity changeDimension(ServerLevel server) {
+- this.isChangingDimension = true;
+- ServerLevel serverLevel = this.serverLevel();
+- ResourceKey<Level> resourceKey = serverLevel.dimension();
+- if (resourceKey == Level.END && server.dimension() == Level.OVERWORLD) {
++ // CraftBukkit start
++ return changeDimension(server, TeleportCause.UNKNOWN);
++ }
++
++ @Nullable
++ public Entity changeDimension(ServerLevel worldserver, PlayerTeleportEvent.TeleportCause cause) {
++ // CraftBukkit end
++ if (this.isSleeping()) return this; // CraftBukkit - SPIGOT-3154
++ // this.isChangingDimension = true; // CraftBukkit - Moved down and into PlayerList#changeDimension
++ ServerLevel worldserver1 = this.serverLevel();
++ ResourceKey<LevelStem> resourcekey = worldserver1.getTypeKey(); // CraftBukkit
++
++ if (resourcekey == LevelStem.END && worldserver != null && worldserver.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit
++ this.isChangingDimension = true; // CraftBukkit - Moved down from above
+ this.unRide();
+ this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ if (!this.wonGame) {
+@@ -753,93 +1034,172 @@
+
+ return this;
+ } else {
+- LevelData levelData = server.getLevelData();
+- this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(server), (byte)3));
+- this.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+- PlayerList playerList = this.server.getPlayerList();
+- playerList.sendPlayerPermissionLevel(this);
+- serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++ // CraftBukkit start
++ /*
++ WorldData worlddata = worldserver.getLevelData();
++
++ this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3));
++ this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
++ PlayerList playerlist = this.server.getPlayerList();
++
++ playerlist.sendPlayerPermissionLevel(this);
++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ this.unsetRemoved();
+- PortalInfo portalInfo = this.findDimensionEntryPoint(server);
+- if (portalInfo != null) {
+- serverLevel.getProfiler().push("moving");
+- if (resourceKey == Level.OVERWORLD && server.dimension() == Level.NETHER) {
++ */
++ // CraftBukkit end
++ PortalInfo shapedetectorshape = this.findDimensionEntryPoint(worldserver);
++
++ if (shapedetectorshape != null) {
++ worldserver1.getProfiler().push("moving");
++ worldserver = shapedetectorshape.world; // CraftBukkit
++ if (worldserver == null) { } else // CraftBukkit - empty to fall through to null to event
++ if (resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit
+ this.enteredNetherPosition = this.position();
+- } else if (server.dimension() == Level.END) {
+- this.createEndPlatform(server, BlockPos.containing(portalInfo.pos));
++ } else if (worldserver.getTypeKey() == LevelStem.END && shapedetectorshape.portalEventInfo != null && shapedetectorshape.portalEventInfo.getCanCreatePortal()) { // CraftBukkit
++ this.createEndPlatform(worldserver, BlockPos.containing(shapedetectorshape.pos));
+ }
++ // CraftBukkit start
++ } else {
++ return null;
++ }
++ Location enter = this.getBukkitEntity().getLocation();
++ Location exit = (worldserver == null) ? null : CraftLocation.toBukkit(shapedetectorshape.pos, worldserver.getWorld(), shapedetectorshape.yRot, shapedetectorshape.xRot);
++ PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, cause);
++ Bukkit.getServer().getPluginManager().callEvent(tpEvent);
++ if (tpEvent.isCancelled() || tpEvent.getTo() == null) {
++ return null;
++ }
++ exit = tpEvent.getTo();
++ worldserver = ((CraftWorld) exit.getWorld()).getHandle();
++ // CraftBukkit end
+
+- serverLevel.getProfiler().pop();
+- serverLevel.getProfiler().push("placing");
+- this.setServerLevel(server);
+- this.connection.teleport(portalInfo.pos.x, portalInfo.pos.y, portalInfo.pos.z, portalInfo.yRot, portalInfo.xRot);
++ worldserver1.getProfiler().pop();
++ worldserver1.getProfiler().push("placing");
++ if (true) { // CraftBukkit
++ this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds
++
++ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3));
++ this.connection.send(new ClientboundChangeDifficultyPacket(this.level().getDifficulty(), this.level().getLevelData().isDifficultyLocked()));
++ PlayerList playerlist = this.server.getPlayerList();
++
++ playerlist.sendPlayerPermissionLevel(this);
++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++ this.unsetRemoved();
++
++ // CraftBukkit end
++ this.setServerLevel(worldserver);
++ this.connection.teleport(exit); // CraftBukkit - use internal teleport without event
+ this.connection.resetPosition();
+- server.addDuringPortalTeleport(this);
+- serverLevel.getProfiler().pop();
+- this.triggerDimensionChangeTriggers(serverLevel);
++ worldserver.addDuringPortalTeleport(this);
++ worldserver1.getProfiler().pop();
++ this.triggerDimensionChangeTriggers(worldserver1);
+ this.connection.send(new ClientboundPlayerAbilitiesPacket(this.getAbilities()));
+- playerList.sendLevelInfo(this, server);
+- playerList.sendAllPlayerInfo(this);
++ playerlist.sendLevelInfo(this, worldserver);
++ playerlist.sendAllPlayerInfo(this);
++ Iterator iterator = this.getActiveEffects().iterator();
+
+- for (MobEffectInstance mobEffectInstance : this.getActiveEffects()) {
+- this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), mobEffectInstance));
++ while (iterator.hasNext()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
++
++ this.connection.send(new ClientboundUpdateMobEffectPacket(this.getId(), mobeffect));
+ }
+
+ this.connection.send(new ClientboundLevelEventPacket(1032, BlockPos.ZERO, 0, false));
+ this.lastSentExp = -1;
+ this.lastSentHealth = -1.0F;
+ this.lastSentFood = -1;
++
++ // CraftBukkit start
++ PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
++ this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
++ // CraftBukkit end
+ }
+
+ return this;
+ }
+ }
+
++ // CraftBukkit start
++ @Override
++ protected CraftPortalEvent callPortalEvent(Entity entity, ServerLevel exitWorldServer, Vec3 exitPosition, TeleportCause cause, int searchRadius, int creationRadius) {
++ Location enter = this.getBukkitEntity().getLocation();
++ Location exit = CraftLocation.toBukkit(exitPosition, exitWorldServer.getWorld(), getYRot(), getXRot());
++ PlayerPortalEvent event = new PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) {
++ return null;
++ }
++ return new CraftPortalEvent(event);
++ }
++ // CraftBukkit end
++
+ private void createEndPlatform(ServerLevel level, BlockPos pos) {
+- BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit
+
+- for (int i = -2; i <= 2; i++) {
+- for (int i1 = -2; i1 <= 2; i1++) {
+- for (int i2 = -1; i2 < 3; i2++) {
+- BlockState blockState = i2 == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
+- level.setBlockAndUpdate(mutableBlockPos.set(pos).move(i1, i2, i), blockState);
++ for (int i = -2; i <= 2; ++i) {
++ for (int j = -2; j <= 2; ++j) {
++ for (int k = -1; k < 3; ++k) {
++ IBlockData iblockdata = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
++
++ blockList.setBlock(blockposition_mutableblockposition.set(pos).move(j, k, i), iblockdata, 3); // CraftBukkit
+ }
+ }
+ }
++ // CraftBukkit start - call portal event
++ org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((List<org.bukkit.block.BlockState>) (List) blockList.getList(), level.getWorld(), this.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
++ level.getCraftServer().getPluginManager().callEvent(portalEvent);
++ if (!portalEvent.isCancelled()) {
++ blockList.updateList();
++ }
++ // CraftBukkit end
++
+ }
+
+ @Override
+- protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel destination, BlockPos findFrom, boolean isToNether, WorldBorder worldBorder) {
+- Optional<BlockUtil.FoundRectangle> optional = super.getExitPortal(destination, findFrom, isToNether, worldBorder);
+- if (optional.isPresent()) {
++ protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel worldserver, BlockPos blockposition, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit
++ Optional<BlockUtil.FoundRectangle> optional = super.getExitPortal(worldserver, blockposition, flag, worldborder, searchRadius, canCreatePortal, createRadius); // CraftBukkit
++
++ if (optional.isPresent() || !canCreatePortal) { // CraftBukkit
+ return optional;
+ } else {
+- Direction.Axis axis = this.level().getBlockState(this.portalEntrancePos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
+- Optional<BlockUtil.FoundRectangle> optional1 = destination.getPortalForcer().createPortal(findFrom, axis);
++ Direction.Axis enumdirection_enumaxis = (Direction.Axis) this.level().getBlockState(this.portalEntrancePos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
++ Optional<BlockUtil.FoundRectangle> optional1 = worldserver.getPortalForcer().createPortal(blockposition, enumdirection_enumaxis, this, createRadius); // CraftBukkit
++
+ if (optional1.isEmpty()) {
+- LOGGER.error("Unable to create a portal, likely target out of worldborder");
++ // EntityPlayer.LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit
+ }
+
+ return optional1;
+ }
+ }
+
+- private void triggerDimensionChangeTriggers(ServerLevel level) {
+- ResourceKey<Level> resourceKey = level.dimension();
+- ResourceKey<Level> resourceKey1 = this.level().dimension();
+- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1);
+- if (resourceKey == Level.NETHER && resourceKey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++ public void triggerDimensionChangeTriggers(ServerLevel level) {
++ ResourceKey<Level> resourcekey = level.dimension();
++ ResourceKey<Level> resourcekey1 = this.level().dimension();
++ // CraftBukkit start
++ ResourceKey<Level> maindimensionkey = CraftDimensionUtil.getMainDimensionKey(level);
++ ResourceKey<Level> maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level());
++
++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1);
++ if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) {
++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1);
++ }
++
++ if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++ // CraftBukkit end
+ CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition);
+ }
+
+- if (resourceKey1 != Level.NETHER) {
++ if (maindimensionkey1 != Level.NETHER) { // CraftBukkit
+ this.enteredNetherPosition = null;
+ }
++
+ }
+
+ @Override
+ public boolean broadcastToPlayer(ServerPlayer player) {
+- return player.isSpectator() ? this.getCamera() == this : !this.isSpectator() && super.broadcastToPlayer(player);
++ return player.isSpectator() ? this.getCamera() == this : (this.isSpectator() ? false : super.broadcastToPlayer(player));
+ }
+
+ @Override
+@@ -848,49 +1208,77 @@
+ this.containerMenu.broadcastChanges();
+ }
+
+- @Override
+- public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at) {
+- Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
+- if (this.isSleeping() || !this.isAlive()) {
+- return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
+- } else if (!this.level().dimensionType().natural()) {
+- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
+- } else if (!this.bedInRange(at, direction)) {
+- return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
+- } else if (this.bedBlocked(at, direction)) {
+- return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+- } else {
+- this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true);
+- if (this.level().isDay()) {
+- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
++ // CraftBukkit start - moved bed result checks from below into separate method
++ private Either<Player.BedSleepingProblem, Unit> getBedResult(BlockPos blockposition, Direction enumdirection) {
++ if (!this.isSleeping() && this.isAlive()) {
++ if (!this.level().dimensionType().natural() || !this.level().dimensionType().bedWorks()) {
++ return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
++ } else if (!this.bedInRange(blockposition, enumdirection)) {
++ return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
++ } else if (this.bedBlocked(blockposition, enumdirection)) {
++ return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+ } else {
+- if (!this.isCreative()) {
+- double d = 8.0;
+- double d1 = 5.0;
+- Vec3 vec3 = Vec3.atBottomCenterOf(at);
+- List<Monster> entitiesOfClass = this.level()
+- .getEntitiesOfClass(
+- Monster.class,
+- new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0),
+- monster -> monster.isPreventingPlayerRest(this)
+- );
+- if (!entitiesOfClass.isEmpty()) {
+- return Either.left(Player.BedSleepingProblem.NOT_SAFE);
++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit
++ if (this.level().isDay()) {
++ return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
++ } else {
++ if (!this.isCreative()) {
++ double d0 = 8.0D;
++ double d1 = 5.0D;
++ Vec3 vec3d = Vec3.atBottomCenterOf(blockposition);
++ List<Monster> list = this.level().getEntitiesOfClass(Monster.class, new AABB(vec3d.x() - 8.0D, vec3d.y() - 5.0D, vec3d.z() - 8.0D, vec3d.x() + 8.0D, vec3d.y() + 5.0D, vec3d.z() + 8.0D), (entitymonster) -> {
++ return entitymonster.isPreventingPlayerRest(this);
++ });
++
++ if (!list.isEmpty()) {
++ return Either.left(Player.BedSleepingProblem.NOT_SAFE);
++ }
+ }
+- }
+
+- Either<Player.BedSleepingProblem, Unit> either = super.startSleepInBed(at).ifRight(unit -> {
+- this.awardStat(Stats.SLEEP_IN_BED);
+- CriteriaTriggers.SLEPT_IN_BED.trigger(this);
+- });
+- if (!this.serverLevel().canSleepThroughNights()) {
+- this.displayClientMessage(Component.translatable("sleep.not_possible"), true);
++ return Either.right(Unit.INSTANCE);
+ }
++ }
++ } else {
++ return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
++ }
++ }
+
+- ((ServerLevel)this.level()).updateSleepingPlayerList();
+- return either;
++ @Override
++ public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockposition, boolean force) {
++ Direction enumdirection = (Direction) this.level().getBlockState(blockposition).getValue(HorizontalDirectionalBlock.FACING);
++ Either<Player.BedSleepingProblem, Unit> bedResult = this.getBedResult(blockposition, enumdirection);
++
++ if (bedResult.left().orElse(null) == Player.BedSleepingProblem.OTHER_PROBLEM) {
++ return bedResult; // return immediately if the result is not bypassable by plugins
++ }
++
++ if (force) {
++ bedResult = Either.right(Unit.INSTANCE);
++ }
++
++ bedResult = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerBedEnterEvent(this, blockposition, bedResult);
++ if (bedResult.left().isPresent()) {
++ return bedResult;
++ }
++
++ {
++ {
++ {
++ Either<Player.BedSleepingProblem, Unit> either = super.startSleepInBed(blockposition, force).ifRight((unit) -> {
++ this.awardStat(Stats.SLEEP_IN_BED);
++ CriteriaTriggers.SLEPT_IN_BED.trigger(this);
++ });
++
++ if (!this.serverLevel().canSleepThroughNights()) {
++ this.displayClientMessage(Component.translatable("sleep.not_possible"), true);
++ }
++
++ ((ServerLevel) this.level()).updateSleepingPlayerList();
++ return either;
++ }
+ }
+ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -904,31 +1292,52 @@
+ }
+
+ private boolean isReachableBedBlock(BlockPos pos) {
+- Vec3 vec3 = Vec3.atBottomCenterOf(pos);
+- return Math.abs(this.getX() - vec3.x()) <= 3.0 && Math.abs(this.getY() - vec3.y()) <= 2.0 && Math.abs(this.getZ() - vec3.z()) <= 3.0;
++ Vec3 vec3d = Vec3.atBottomCenterOf(pos);
++
++ return Math.abs(this.getX() - vec3d.x()) <= 3.0D && Math.abs(this.getY() - vec3d.y()) <= 2.0D && Math.abs(this.getZ() - vec3d.z()) <= 3.0D;
+ }
+
+ private boolean bedBlocked(BlockPos pos, Direction direction) {
+- BlockPos blockPos = pos.above();
+- return !this.freeAt(blockPos) || !this.freeAt(blockPos.relative(direction.getOpposite()));
++ BlockPos blockposition1 = pos.above();
++
++ return !this.freeAt(blockposition1) || !this.freeAt(blockposition1.relative(direction.getOpposite()));
+ }
+
+ @Override
+ public void stopSleepInBed(boolean wakeImmediately, boolean updateLevelForSleepingPlayers) {
++ if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one!
++ // CraftBukkit start - fire PlayerBedLeaveEvent
++ CraftPlayer player = this.getBukkitEntity();
++ BlockPos bedPosition = this.getSleepingPos().orElse(null);
++
++ org.bukkit.block.Block bed;
++ if (bedPosition != null) {
++ bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ());
++ } else {
++ bed = this.level().getWorld().getBlockAt(player.getLocation());
++ }
++
++ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed, true);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ if (this.isSleeping()) {
+ this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2));
+ }
+
+ super.stopSleepInBed(wakeImmediately, updateLevelForSleepingPlayers);
+ if (this.connection != null) {
+- this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
++ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), TeleportCause.EXIT_BED); // CraftBukkit
+ }
++
+ }
+
+ @Override
+- public void dismountTo(double x, double y, double z) {
++ public void dismountTo(double x, double d1, double y) {
+ this.removeVehicle();
+- this.setPos(x, y, z);
++ this.setPos(x, d1, y);
+ }
+
+ @Override
+@@ -937,21 +1346,22 @@
+ }
+
+ @Override
+- protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
+- }
++ protected void checkFallDamage(double y, boolean flag, IBlockData onGround, BlockPos state) {}
+
+ @Override
+ protected void onChangedBlock(BlockPos pos) {
+ if (!this.isSpectator()) {
+ super.onChangedBlock(pos);
+ }
++
+ }
+
+- public void doCheckFallDamage(double movementX, double movementY, double movementZ, boolean onGround) {
++ public void doCheckFallDamage(double movementX, double d1, double movementY, boolean flag) {
+ if (!this.touchingUnloadedChunk()) {
+- this.checkSupportingBlock(onGround, new Vec3(movementX, movementY, movementZ));
+- BlockPos onPosLegacy = this.getOnPosLegacy();
+- super.checkFallDamage(movementY, onGround, this.level().getBlockState(onPosLegacy), onPosLegacy);
++ this.checkSupportingBlock(flag, new Vec3(movementX, d1, movementY));
++ BlockPos blockposition = this.getOnPosLegacy();
++
++ super.checkFallDamage(d1, flag, this.level().getBlockState(blockposition), blockposition);
+ }
+ }
+
+@@ -960,6 +1370,7 @@
+ if (this.level().tickRateManager().runsNormally()) {
+ super.pushEntities();
+ }
++
+ }
+
+ @Override
+@@ -968,32 +1379,57 @@
+ this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText));
+ }
+
+- private void nextContainerCounter() {
++ public int nextContainerCounter() { // CraftBukkit - void -> int
+ this.containerCounter = this.containerCounter % 100 + 1;
++ return containerCounter; // CraftBukkit
+ }
+
+ @Override
+- public OptionalInt openMenu(@Nullable MenuProvider menu) {
++ public OptionalInt openMenu(@Nullable ITileInventory menu) {
+ if (menu == null) {
+ return OptionalInt.empty();
+ } else {
++ // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...)
++ /*
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer();
+ }
++ */
++ // CraftBukkit end
+
+ this.nextContainerCounter();
+- AbstractContainerMenu abstractContainerMenu = menu.createMenu(this.containerCounter, this.getInventory(), this);
+- if (abstractContainerMenu == null) {
++ AbstractContainerMenu container = menu.createMenu(this.containerCounter, this.getInventory(), this);
++
++ // CraftBukkit start - Inventory open hook
++ if (container != null) {
++ container.setTitle(menu.getDisplayName());
++
++ boolean cancelled = false;
++ container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled);
++ if (container == null && !cancelled) { // Let pre-cancelled events fall through
++ // SPIGOT-5263 - close chest if cancelled
++ if (menu instanceof Container) {
++ ((Container) menu).stopOpen(this);
++ } else if (menu instanceof ChestBlock.DoubleInventory) {
++ // SPIGOT-5355 - double chests too :(
++ ((ChestBlock.DoubleInventory) menu).inventorylargechest.stopOpen(this);
++ }
++ return OptionalInt.empty();
++ }
++ }
++ // CraftBukkit end
++ if (container == null) {
+ if (this.isSpectator()) {
+ this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true);
+ }
+
+ return OptionalInt.empty();
+ } else {
+- this.connection
+- .send(new ClientboundOpenScreenPacket(abstractContainerMenu.containerId, abstractContainerMenu.getType(), menu.getDisplayName()));
+- this.initMenu(abstractContainerMenu);
+- this.containerMenu = abstractContainerMenu;
++ // CraftBukkit start
++ this.containerMenu = container;
++ this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle()));
++ // CraftBukkit end
++ this.initMenu(container);
+ return OptionalInt.of(this.containerCounter);
+ }
+ }
+@@ -1006,18 +1442,29 @@
+
+ @Override
+ public void openHorseInventory(AbstractHorse horse, Container inventory) {
++ // CraftBukkit start - Inventory open hook
++ this.nextContainerCounter();
++ AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse);
++ container.setTitle(horse.getDisplayName());
++ container = CraftEventFactory.callInventoryOpenEvent(this, container);
++
++ if (container == null) {
++ inventory.stopOpen(this);
++ return;
++ }
++ // CraftBukkit end
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer();
+ }
+
+- this.nextContainerCounter();
++ // this.nextContainerCounter(); // CraftBukkit - moved up
+ this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventory.getContainerSize(), horse.getId()));
+- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse);
++ this.containerMenu = container; // CraftBukkit
+ this.initMenu(this.containerMenu);
+ }
+
+ @Override
+- public void openItemGui(ItemStack stack, InteractionHand hand) {
++ public void openItemGui(ItemStack stack, EnumHand hand) {
+ if (stack.is(Items.WRITTEN_BOOK)) {
+ if (WrittenBookItem.resolveBookComponents(stack, this.createCommandSourceStack(), this)) {
+ this.containerMenu.broadcastChanges();
+@@ -1025,6 +1472,7 @@
+
+ this.connection.send(new ClientboundOpenBookPacket(hand));
+ }
++
+ }
+
+ @Override
+@@ -1034,6 +1482,7 @@
+
+ @Override
+ public void closeContainer() {
++ CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit
+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
+ this.doCloseContainer();
+ }
+@@ -1056,110 +1505,130 @@
+ }
+
+ this.jumping = jumping;
++ // CraftBukkit start
++ if (sneaking != this.isShiftKeyDown()) {
++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getBukkitEntity(), sneaking);
++ this.server.server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ this.setShiftKeyDown(sneaking);
+ }
++
+ }
+
+ @Override
+ public void travel(Vec3 travelVector) {
+- double x = this.getX();
+- double y = this.getY();
+- double z = this.getZ();
++ double d0 = this.getX();
++ double d1 = this.getY();
++ double d2 = this.getZ();
++
+ super.travel(travelVector);
+- this.checkMovementStatistics(this.getX() - x, this.getY() - y, this.getZ() - z);
++ this.checkMovementStatistics(this.getX() - d0, this.getY() - d1, this.getZ() - d2);
+ }
+
+ @Override
+ public void rideTick() {
+- double x = this.getX();
+- double y = this.getY();
+- double z = this.getZ();
++ double d0 = this.getX();
++ double d1 = this.getY();
++ double d2 = this.getZ();
++
+ super.rideTick();
+- this.checkRidingStatistics(this.getX() - x, this.getY() - y, this.getZ() - z);
++ this.checkRidingStatistics(this.getX() - d0, this.getY() - d1, this.getZ() - d2);
+ }
+
+- public void checkMovementStatistics(double d, double d1, double d2) {
+- if (!this.isPassenger() && !didNotMove(d, d1, d2)) {
++ public void checkMovementStatistics(double d0, double d1, double d2) {
++ if (!this.isPassenger() && !didNotMove(d0, d1, d2)) {
++ int i;
++
+ if (this.isSwimming()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.SWIM_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
++ if (i > 0) {
++ this.awardStat(Stats.SWIM_ONE_CM, i);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isEyeInFluid(FluidTags.WATER)) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
++ if (i > 0) {
++ this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isInWater()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.WALK_ON_WATER_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ i = Math.round((float) Math.sqrt(d0 * d0 + d2 * d2) * 100.0F);
++ if (i > 0) {
++ this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.onClimbable()) {
+- if (d1 > 0.0) {
+- this.awardStat(Stats.CLIMB_ONE_CM, (int)Math.round(d1 * 100.0));
++ if (d1 > 0.0D) {
++ this.awardStat(Stats.CLIMB_ONE_CM, (int) Math.round(d1 * 100.0D));
+ }
+ } else if (this.onGround()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d2 * d2) * 100.0F);
+- if (rounded > 0) {
++ i = Math.round((float) Math.sqrt(d0 * d0 + d2 * d2) * 100.0F);
++ if (i > 0) {
+ if (this.isSprinting()) {
+- this.awardStat(Stats.SPRINT_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.1F * (float)rounded * 0.01F);
++ this.awardStat(Stats.SPRINT_ONE_CM, i);
++ this.causeFoodExhaustion(0.1F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent
+ } else if (this.isCrouching()) {
+- this.awardStat(Stats.CROUCH_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.0F * (float)rounded * 0.01F);
++ this.awardStat(Stats.CROUCH_ONE_CM, i);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent
+ } else {
+- this.awardStat(Stats.WALK_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.0F * (float)rounded * 0.01F);
++ this.awardStat(Stats.WALK_ONE_CM, i);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent
+ }
+ }
+ } else if (this.isFallFlying()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- this.awardStat(Stats.AVIATE_ONE_CM, rounded);
++ i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
++ this.awardStat(Stats.AVIATE_ONE_CM, i);
+ } else {
+- int rounded = Math.round((float)Math.sqrt(d * d + d2 * d2) * 100.0F);
+- if (rounded > 25) {
+- this.awardStat(Stats.FLY_ONE_CM, rounded);
++ i = Math.round((float) Math.sqrt(d0 * d0 + d2 * d2) * 100.0F);
++ if (i > 25) {
++ this.awardStat(Stats.FLY_ONE_CM, i);
+ }
+ }
++
+ }
+ }
+
+- private void checkRidingStatistics(double d, double d1, double d2) {
+- if (this.isPassenger() && !didNotMove(d, d1, d2)) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- Entity vehicle = this.getVehicle();
+- if (vehicle instanceof AbstractMinecart) {
+- this.awardStat(Stats.MINECART_ONE_CM, rounded);
+- } else if (vehicle instanceof Boat) {
+- this.awardStat(Stats.BOAT_ONE_CM, rounded);
+- } else if (vehicle instanceof Pig) {
+- this.awardStat(Stats.PIG_ONE_CM, rounded);
+- } else if (vehicle instanceof AbstractHorse) {
+- this.awardStat(Stats.HORSE_ONE_CM, rounded);
+- } else if (vehicle instanceof Strider) {
+- this.awardStat(Stats.STRIDER_ONE_CM, rounded);
++ private void checkRidingStatistics(double d0, double d1, double d2) {
++ if (this.isPassenger() && !didNotMove(d0, d1, d2)) {
++ int i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
++ Entity entity = this.getVehicle();
++
++ if (entity instanceof AbstractMinecart) {
++ this.awardStat(Stats.MINECART_ONE_CM, i);
++ } else if (entity instanceof Boat) {
++ this.awardStat(Stats.BOAT_ONE_CM, i);
++ } else if (entity instanceof Pig) {
++ this.awardStat(Stats.PIG_ONE_CM, i);
++ } else if (entity instanceof AbstractHorse) {
++ this.awardStat(Stats.HORSE_ONE_CM, i);
++ } else if (entity instanceof Strider) {
++ this.awardStat(Stats.STRIDER_ONE_CM, i);
+ }
++
+ }
+ }
+
+- private static boolean didNotMove(double d, double d1, double d2) {
+- return d == 0.0 && d1 == 0.0 && d2 == 0.0;
++ private static boolean didNotMove(double d0, double d1, double d2) {
++ return d0 == 0.0D && d1 == 0.0D && d2 == 0.0D;
+ }
+
+ @Override
+ public void awardStat(Stat<?> stat, int amount) {
+ this.stats.increment(this, stat, amount);
+- this.getScoreboard().forAllObjectives(stat, this, scoreAccess -> scoreAccess.add(amount));
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, (scoreaccess) -> {
++ scoreaccess.add(amount);
++ });
+ }
+
+ @Override
+ public void resetStat(Stat<?> stat) {
+ this.stats.setValue(this, stat, 0);
+- this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset);
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead
+ }
+
+ @Override
+@@ -1168,15 +1637,16 @@
+ }
+
+ @Override
+- public void triggerRecipeCrafted(RecipeHolder<?> recipeHolder, List<ItemStack> list) {
+- CriteriaTriggers.RECIPE_CRAFTED.trigger(this, recipeHolder.id(), list);
++ public void triggerRecipeCrafted(RecipeHolder<?> recipeholder, List<ItemStack> list) {
++ CriteriaTriggers.RECIPE_CRAFTED.trigger(this, recipeholder.id(), list);
+ }
+
+ @Override
+ public void awardRecipesByKey(List<ResourceLocation> list) {
+- List<RecipeHolder<?>> list1 = list.stream()
+- .flatMap(resourceLocation -> this.server.getRecipeManager().byKey(resourceLocation).stream())
+- .collect(Collectors.toList());
++ List<RecipeHolder<?>> list1 = (List) list.stream().flatMap((minecraftkey) -> {
++ return this.server.getRecipeManager().byKey(minecraftkey).stream();
++ }).collect(Collectors.toList());
++
+ this.awardRecipes(list1);
+ }
+
+@@ -1197,6 +1667,7 @@
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, false);
+ }
++
+ }
+
+ public boolean hasDisconnected() {
+@@ -1205,6 +1676,7 @@
+
+ public void resetSentInfo() {
+ this.lastSentHealth = -1.0E8F;
++ this.lastSentExp = -1; // CraftBukkit - Added to reset
+ }
+
+ @Override
+@@ -1215,9 +1687,10 @@
+ @Override
+ protected void completeUsingItem() {
+ if (!this.useItem.isEmpty() && this.isUsingItem()) {
+- this.connection.send(new ClientboundEntityEventPacket(this, (byte)9));
++ this.connection.send(new ClientboundEntityEventPacket(this, (byte) 9));
+ super.completeUsingItem();
+ }
++
+ }
+
+ @Override
+@@ -1227,8 +1700,9 @@
+ }
+
+ public void lookAt(EntityAnchorArgument.Anchor fromAnchor, Entity entity, EntityAnchorArgument.Anchor toAnchor) {
+- Vec3 vec3 = toAnchor.apply(entity);
+- super.lookAt(fromAnchor, vec3);
++ Vec3 vec3d = toAnchor.apply(entity);
++
++ super.lookAt(fromAnchor, vec3d);
+ this.connection.send(new ClientboundPlayerLookAtPacket(fromAnchor, entity, toAnchor));
+ }
+
+@@ -1256,11 +1730,11 @@
+
+ this.enchantmentSeed = that.enchantmentSeed;
+ this.enderChestInventory = that.enderChestInventory;
+- this.getEntityData().set(DATA_PLAYER_MODE_CUSTOMISATION, that.getEntityData().get(DATA_PLAYER_MODE_CUSTOMISATION));
++ this.getEntityData().set(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION, (Byte) that.getEntityData().get(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION));
+ this.lastSentExp = -1;
+ this.lastSentHealth = -1.0F;
+ this.lastSentFood = -1;
+- this.recipeBook.copyOverData(that.recipeBook);
++ // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit
+ this.seenCredits = that.seenCredits;
+ this.enteredNetherPosition = that.enteredNetherPosition;
+ this.chunkTrackingView = that.chunkTrackingView;
+@@ -1296,41 +1770,48 @@
+ this.levitationStartPos = null;
+ }
+
+- CriteriaTriggers.EFFECTS_CHANGED.trigger(this, null);
++ CriteriaTriggers.EFFECTS_CHANGED.trigger(this, (Entity) null);
+ }
+
+ @Override
+- public void teleportTo(double x, double y, double z) {
+- this.connection.teleport(x, y, z, this.getYRot(), this.getXRot(), RelativeMovement.ROTATION);
++ public void teleportTo(double x, double d1, double y) {
++ this.connection.teleport(x, d1, y, this.getYRot(), this.getXRot(), RelativeMovement.ROTATION);
+ }
+
+ @Override
+- public void teleportRelative(double dx, double dy, double dz) {
+- this.connection.teleport(this.getX() + dx, this.getY() + dy, this.getZ() + dz, this.getYRot(), this.getXRot(), RelativeMovement.ALL);
++ public void teleportRelative(double dx, double d1, double dy) {
++ this.connection.teleport(this.getX() + dx, this.getY() + d1, this.getZ() + dy, this.getYRot(), this.getXRot(), RelativeMovement.ALL);
+ }
+
+ @Override
+- public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<RelativeMovement> relativeMovements, float yRot, float xRot) {
+- ChunkPos chunkPos = new ChunkPos(BlockPos.containing(x, y, z));
+- level.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkPos, 1, this.getId());
++ public boolean teleportTo(ServerLevel level, double x, double d1, double y, Set<RelativeMovement> set, float z, float f1) {
++ // CraftBukkit start
++ return teleportTo(level, x, d1, y, set, z, f1, TeleportCause.UNKNOWN);
++ }
++
++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1, TeleportCause cause) {
++ // CraftBukkit end
++ ChunkPos chunkcoordintpair = new ChunkPos(BlockPos.containing(d0, d1, d2));
++
++ worldserver.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkcoordintpair, 1, this.getId());
+ this.stopRiding();
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, true);
+ }
+
+- if (level == this.level()) {
+- this.connection.teleport(x, y, z, yRot, xRot, relativeMovements);
++ if (worldserver == this.level()) {
++ this.connection.teleport(d0, d1, d2, f, f1, set, cause); // CraftBukkit
+ } else {
+- this.teleportTo(level, x, y, z, yRot, xRot);
++ this.teleportTo(worldserver, d0, d1, d2, f, f1, cause); // CraftBukkit
+ }
+
+- this.setYHeadRot(yRot);
++ this.setYHeadRot(f);
+ return true;
+ }
+
+ @Override
+- public void moveTo(double x, double y, double z) {
+- super.moveTo(x, y, z);
++ public void moveTo(double x, double d1, double y) {
++ super.moveTo(x, d1, y);
+ this.connection.resetPosition();
+ }
+
+@@ -1353,14 +1834,14 @@
+ }
+
+ public ServerLevel serverLevel() {
+- return (ServerLevel)this.level();
++ return (ServerLevel) this.level();
+ }
+
+ public boolean setGameMode(GameType gameMode) {
+ if (!this.gameMode.changeGameModeForPlayer(gameMode)) {
+ return false;
+ } else {
+- this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float)gameMode.getId()));
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId()));
+ if (gameMode == GameType.SPECTATOR) {
+ this.removeEntitiesOnShoulder();
+ this.stopRiding();
+@@ -1391,24 +1872,17 @@
+
+ public void sendSystemMessage(Component component, boolean bypassHiddenChat) {
+ if (this.acceptsSystemMessages(bypassHiddenChat)) {
+- this.connection
+- .send(
+- new ClientboundSystemChatPacket(component, bypassHiddenChat),
+- PacketSendListener.exceptionallySend(
+- () -> {
+- if (this.acceptsSystemMessages(false)) {
+- int i = 256;
+- String string = component.getString(256);
+- Component component1 = Component.literal(string).withStyle(ChatFormatting.YELLOW);
+- return new ClientboundSystemChatPacket(
+- Component.translatable("multiplayer.message_not_delivered", component1).withStyle(ChatFormatting.RED), false
+- );
+- } else {
+- return null;
+- }
+- }
+- )
+- );
++ this.connection.send(new ClientboundSystemChatPacket(component, bypassHiddenChat), PacketSendListener.exceptionallySend(() -> {
++ if (this.acceptsSystemMessages(false)) {
++ boolean flag1 = true;
++ String s = component.getString(256);
++ MutableComponent ichatmutablecomponent = Component.literal(s).withStyle(ChatFormatting.YELLOW);
++
++ return new ClientboundSystemChatPacket(Component.translatable("multiplayer.message_not_delivered", ichatmutablecomponent).withStyle(ChatFormatting.RED), false);
++ } else {
++ return null;
++ }
++ }));
+ }
+ }
+
+@@ -1416,31 +1890,47 @@
+ if (this.acceptsChatMessages()) {
+ message.sendToPlayer(this, filtered, boundType);
+ }
++
+ }
+
+ public String getIpAddress() {
+- return this.connection.getRemoteAddress() instanceof InetSocketAddress inetSocketAddress
+- ? InetAddresses.toAddrString(inetSocketAddress.getAddress())
+- : "<unknown>";
++ SocketAddress socketaddress = this.connection.getRemoteAddress();
++
++ if (socketaddress instanceof InetSocketAddress) {
++ InetSocketAddress inetsocketaddress = (InetSocketAddress) socketaddress;
++
++ return InetAddresses.toAddrString(inetsocketaddress.getAddress());
++ } else {
++ return "<unknown>";
++ }
+ }
+
+- public void updateOptions(ClientInformation clientInformation) {
+- this.language = clientInformation.language();
+- this.requestedViewDistance = clientInformation.viewDistance();
+- this.chatVisibility = clientInformation.chatVisibility();
+- this.canChatColor = clientInformation.chatColors();
+- this.textFilteringEnabled = clientInformation.textFilteringEnabled();
+- this.allowsListing = clientInformation.allowsListing();
+- this.getEntityData().set(DATA_PLAYER_MODE_CUSTOMISATION, (byte)clientInformation.modelCustomisation());
+- this.getEntityData().set(DATA_PLAYER_MAIN_HAND, (byte)clientInformation.mainHand().getId());
++ public void updateOptions(ClientInformation clientinformation) {
++ // CraftBukkit start
++ if (getMainArm() != clientinformation.mainHand()) {
++ PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
++ this.server.server.getPluginManager().callEvent(event);
++ }
++ if (!this.language.equals(clientinformation.language())) {
++ PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(getBukkitEntity(), clientinformation.language());
++ this.server.server.getPluginManager().callEvent(event);
++ }
++ // CraftBukkit end
++ this.language = clientinformation.language();
++ this.requestedViewDistance = clientinformation.viewDistance();
++ this.chatVisibility = clientinformation.chatVisibility();
++ this.canChatColor = clientinformation.chatColors();
++ this.textFilteringEnabled = clientinformation.textFilteringEnabled();
++ this.allowsListing = clientinformation.allowsListing();
++ this.getEntityData().set(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION, (byte) clientinformation.modelCustomisation());
++ this.getEntityData().set(ServerPlayer.DATA_PLAYER_MAIN_HAND, (byte) clientinformation.mainHand().getId());
+ }
+
+ public ClientInformation clientInformation() {
+- int i = this.getEntityData().get(DATA_PLAYER_MODE_CUSTOMISATION);
+- HumanoidArm humanoidArm = HumanoidArm.BY_ID.apply(this.getEntityData().get(DATA_PLAYER_MAIN_HAND));
+- return new ClientInformation(
+- this.language, this.requestedViewDistance, this.chatVisibility, this.canChatColor, i, humanoidArm, this.textFilteringEnabled, this.allowsListing
+- );
++ byte b0 = (Byte) this.getEntityData().get(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION);
++ HumanoidArm enummainhand = (HumanoidArm) HumanoidArm.BY_ID.apply((Byte) this.getEntityData().get(ServerPlayer.DATA_PLAYER_MAIN_HAND));
++
++ return new ClientInformation(this.language, this.requestedViewDistance, this.chatVisibility, this.canChatColor, b0, enummainhand, this.textFilteringEnabled, this.allowsListing);
+ }
+
+ public boolean canChatInColor() {
+@@ -1452,7 +1942,7 @@
+ }
+
+ private boolean acceptsSystemMessages(boolean bypassHiddenChat) {
+- return this.chatVisibility != ChatVisiblity.HIDDEN || bypassHiddenChat;
++ return this.chatVisibility == ChatVisiblity.HIDDEN ? bypassHiddenChat : true;
+ }
+
+ private boolean acceptsChatMessages() {
+@@ -1464,12 +1954,7 @@
+ }
+
+ public void sendServerStatus(ServerStatus serverStatus) {
+- this.connection
+- .send(
+- new ClientboundServerDataPacket(
+- serverStatus.description(), serverStatus.favicon().map(ServerStatus.Favicon::iconBytes), serverStatus.enforcesSecureChat()
+- )
+- );
++ this.connection.send(new ClientboundServerDataPacket(serverStatus.description(), serverStatus.favicon().map(ServerStatus.a::iconBytes), serverStatus.enforcesSecureChat()));
+ }
+
+ @Override
+@@ -1497,18 +1982,24 @@
+ } else {
+ super.updateInvisibilityStatus();
+ }
++
+ }
+
+ public Entity getCamera() {
+- return (Entity)(this.camera == null ? this : this.camera);
++ return (Entity) (this.camera == null ? this : this.camera);
+ }
+
+ public void setCamera(@Nullable Entity entityToSpectate) {
+- Entity camera = this.getCamera();
+- this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate);
+- if (camera != this.camera) {
+- if (this.camera.level() instanceof ServerLevel serverLevel) {
+- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot());
++ Entity entity1 = this.getCamera();
++
++ this.camera = (Entity) (entityToSpectate == null ? this : entityToSpectate);
++ if (entity1 != this.camera) {
++ Level world = this.camera.level();
++
++ if (world instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) world;
++
++ this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), TeleportCause.SPECTATE); // CraftBukkit
+ }
+
+ if (entityToSpectate != null) {
+@@ -1518,6 +2009,7 @@
+ this.connection.send(new ClientboundSetCameraPacket(this.camera));
+ this.connection.resetPosition();
+ }
++
+ }
+
+ @Override
+@@ -1525,6 +2017,7 @@
+ if (!this.isChangingDimension) {
+ super.processPortalCooldown();
+ }
++
+ }
+
+ @Override
+@@ -1534,6 +2027,7 @@
+ } else {
+ super.attack(targetEntity);
+ }
++
+ }
+
+ public long getLastActionTime() {
+@@ -1542,11 +2036,11 @@
+
+ @Nullable
+ public Component getTabListDisplayName() {
+- return null;
++ return listName; // CraftBukkit
+ }
+
+ @Override
+- public void swing(InteractionHand hand) {
++ public void swing(EnumHand hand) {
+ super.swing(hand);
+ this.resetAttackStrengthTicker();
+ }
+@@ -1563,27 +2057,39 @@
+ return this.advancements;
+ }
+
+- public void teleportTo(ServerLevel newLevel, double x, double y, double z, float yaw, float pitch) {
++ // CraftBukkit start
++ public void teleportTo(ServerLevel newLevel, double x, double d1, double y, float f, float z) {
++ this.teleportTo(newLevel, x, d1, y, f, z, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++ }
++
++ public void teleportTo(ServerLevel worldserver, double d0, double d1, double d2, float f, float f1, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++ // CraftBukkit end
+ this.setCamera(this);
+ this.stopRiding();
+- if (newLevel == this.level()) {
+- this.connection.teleport(x, y, z, yaw, pitch);
++ /* CraftBukkit start - replace with bukkit handling for multi-world
++ if (worldserver == this.level()) {
++ this.connection.teleport(d0, d1, d2, f, f1);
+ } else {
+- ServerLevel serverLevel = this.serverLevel();
+- LevelData levelData = newLevel.getLevelData();
+- this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(newLevel), (byte)3));
+- this.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
++ WorldServer worldserver1 = this.serverLevel();
++ WorldData worlddata = worldserver.getLevelData();
++
++ this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3));
++ this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
+ this.server.getPlayerList().sendPlayerPermissionLevel(this);
+- serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ this.unsetRemoved();
+- this.moveTo(x, y, z, yaw, pitch);
+- this.setServerLevel(newLevel);
+- newLevel.addDuringCommandTeleport(this);
+- this.triggerDimensionChangeTriggers(serverLevel);
+- this.connection.teleport(x, y, z, yaw, pitch);
+- this.server.getPlayerList().sendLevelInfo(this, newLevel);
++ this.moveTo(d0, d1, d2, f, f1);
++ this.setServerLevel(worldserver);
++ worldserver.addDuringCommandTeleport(this);
++ this.triggerDimensionChangeTriggers(worldserver1);
++ this.connection.teleport(d0, d1, d2, f, f1);
++ this.server.getPlayerList().sendLevelInfo(this, worldserver);
+ this.server.getPlayerList().sendAllPlayerInfo(this);
+ }
++ */
++ this.getBukkitEntity().teleport(new Location(worldserver.getWorld(), d0, d1, d2, f, f1), cause);
++ // CraftBukkit end
++
+ }
+
+ @Nullable
+@@ -1604,22 +2110,50 @@
+ }
+
+ public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage) {
+- if (position != null) {
+- boolean flag = position.equals(this.respawnPosition) && dimension.equals(this.respawnDimension);
+- if (sendMessage && !flag) {
++ // CraftBukkit start
++ this.setRespawnPosition(dimension, position, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN);
++ }
++
++ public void setRespawnPosition(ResourceKey<Level> resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) {
++ ServerLevel newWorld = this.server.getLevel(resourcekey);
++ Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null;
++
++ PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ newSpawn = event.getNewSpawn();
++ flag = event.isForced();
++
++ if (newSpawn != null) {
++ resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension();
++ blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ());
++ f = newSpawn.getYaw();
++ } else {
++ resourcekey = Level.OVERWORLD;
++ blockposition = null;
++ f = 0.0F;
++ }
++ // CraftBukkit end
++ if (blockposition != null) {
++ boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension);
++
++ if (flag1 && !flag2) {
+ this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn"));
+ }
+
+- this.respawnPosition = position;
+- this.respawnDimension = dimension;
+- this.respawnAngle = angle;
+- this.respawnForced = forced;
++ this.respawnPosition = blockposition;
++ this.respawnDimension = resourcekey;
++ this.respawnAngle = f;
++ this.respawnForced = flag;
+ } else {
+ this.respawnPosition = null;
+ this.respawnDimension = Level.OVERWORLD;
+ this.respawnAngle = 0.0F;
+ this.respawnForced = false;
+ }
++
+ }
+
+ public SectionPos getLastSectionPos() {
+@@ -1634,37 +2168,34 @@
+ return this.chunkTrackingView;
+ }
+
+- public void setChunkTrackingView(ChunkTrackingView chunkTrackingView) {
+- this.chunkTrackingView = chunkTrackingView;
++ public void setChunkTrackingView(ChunkTrackingView chunktrackingview) {
++ this.chunkTrackingView = chunktrackingview;
+ }
+
+ @Override
+ public void playNotifySound(SoundEvent sound, SoundSource source, float volume, float pitch) {
+- this.connection
+- .send(
+- new ClientboundSoundPacket(
+- BuiltInRegistries.SOUND_EVENT.wrapAsHolder(sound), source, this.getX(), this.getY(), this.getZ(), volume, pitch, this.random.nextLong()
+- )
+- );
++ this.connection.send(new ClientboundSoundPacket(BuiltInRegistries.SOUND_EVENT.wrapAsHolder(sound), source, this.getX(), this.getY(), this.getZ(), volume, pitch, this.random.nextLong()));
+ }
+
+ @Override
+ public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean traceItem) {
+- ItemEntity itemEntity = super.drop(droppedItem, dropAround, traceItem);
+- if (itemEntity == null) {
++ ItemEntity entityitem = super.drop(droppedItem, dropAround, traceItem);
++
++ if (entityitem == null) {
+ return null;
+ } else {
+- this.level().addFreshEntity(itemEntity);
+- ItemStack item = itemEntity.getItem();
++ this.level().addFreshEntity(entityitem);
++ ItemStack itemstack1 = entityitem.getItem();
++
+ if (traceItem) {
+- if (!item.isEmpty()) {
+- this.awardStat(Stats.ITEM_DROPPED.get(item.getItem()), droppedItem.getCount());
++ if (!itemstack1.isEmpty()) {
++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), droppedItem.getCount());
+ }
+
+ this.awardStat(Stats.DROP);
+ }
+
+- return itemEntity;
++ return entityitem;
+ }
+ }
+
+@@ -1683,25 +2214,23 @@
+ }
+
+ private GameType calculateGameModeForNewPlayer(@Nullable GameType gameType) {
+- GameType forcedGameType = this.server.getForcedGameType();
+- if (forcedGameType != null) {
+- return forcedGameType;
+- } else {
+- return gameType != null ? gameType : this.server.getDefaultGameType();
+- }
++ GameType enumgamemode1 = this.server.getForcedGameType();
++
++ return enumgamemode1 != null ? enumgamemode1 : (gameType != null ? gameType : this.server.getDefaultGameType());
+ }
+
+ public void loadGameTypes(@Nullable CompoundTag tag) {
+- this.gameMode
+- .setGameModeForPlayer(this.calculateGameModeForNewPlayer(readPlayerMode(tag, "playerGameType")), readPlayerMode(tag, "previousPlayerGameType"));
++ this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(readPlayerMode(tag, "playerGameType")), readPlayerMode(tag, "previousPlayerGameType"));
+ }
+
+ private void storeGameTypes(CompoundTag tag) {
+ tag.putInt("playerGameType", this.gameMode.getGameModeForPlayer().getId());
+- GameType previousGameModeForPlayer = this.gameMode.getPreviousGameModeForPlayer();
+- if (previousGameModeForPlayer != null) {
+- tag.putInt("previousPlayerGameType", previousGameModeForPlayer.getId());
++ GameType enumgamemode = this.gameMode.getPreviousGameModeForPlayer();
++
++ if (enumgamemode != null) {
++ tag.putInt("previousPlayerGameType", enumgamemode.getId());
+ }
++
+ }
+
+ @Override
+@@ -1710,7 +2239,7 @@
+ }
+
+ public boolean shouldFilterMessageTo(ServerPlayer player) {
+- return player != this && (this.textFilteringEnabled || player.textFilteringEnabled);
++ return player == this ? false : this.textFilteringEnabled || player.textFilteringEnabled;
+ }
+
+ @Override
+@@ -1725,10 +2254,13 @@
+ }
+
+ public boolean drop(boolean dropStack) {
+- Inventory inventory = this.getInventory();
+- ItemStack itemStack = inventory.removeFromSelected(dropStack);
+- this.containerMenu.findSlot(inventory, inventory.selected).ifPresent(i -> this.containerMenu.setRemoteSlot(i, inventory.getSelected()));
+- return this.drop(itemStack, false, true) != null;
++ Inventory playerinventory = this.getInventory();
++ ItemStack itemstack = playerinventory.removeFromSelected(dropStack);
++
++ this.containerMenu.findSlot(playerinventory, playerinventory.selected).ifPresent((i) -> {
++ this.containerMenu.setRemoteSlot(i, playerinventory.getSelected());
++ });
++ return this.drop(itemstack, false, true) != null;
+ }
+
+ public boolean allowsListing() {
+@@ -1743,10 +2275,12 @@
+ @Override
+ public void onItemPickup(ItemEntity itemEntity) {
+ super.onItemPickup(itemEntity);
+- Entity owner = itemEntity.getOwner();
+- if (owner != null) {
+- CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_PLAYER.trigger(this, itemEntity.getItem(), owner);
++ Entity entity = itemEntity.getOwner();
++
++ if (entity != null) {
++ CriteriaTriggers.THROWN_ITEM_PICKED_UP_BY_PLAYER.trigger(this, itemEntity.getItem(), entity);
+ }
++
+ }
+
+ public void setChatSession(RemoteChatSession chatSession) {
+@@ -1759,8 +2293,8 @@
+ }
+
+ @Override
+- public void indicateDamage(double xDistance, double zDistance) {
+- this.hurtDir = (float)(Mth.atan2(zDistance, xDistance) * 180.0F / (float)Math.PI - (double)this.getYRot());
++ public void indicateDamage(double xDistance, double d1) {
++ this.hurtDir = (float) (Mth.atan2(d1, xDistance) * 57.2957763671875D - (double) this.getYRot());
+ this.connection.send(new ClientboundHurtAnimationPacket(this));
+ }
+
+@@ -1771,9 +2305,14 @@
+ } else {
+ vehicle.positionRider(this);
+ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+- if (vehicle instanceof LivingEntity livingEntity) {
+- for (MobEffectInstance mobEffectInstance : livingEntity.getActiveEffects()) {
+- this.connection.send(new ClientboundUpdateMobEffectPacket(vehicle.getId(), mobEffectInstance));
++ if (vehicle instanceof LivingEntity) {
++ LivingEntity entityliving = (LivingEntity) vehicle;
++ Iterator iterator = entityliving.getActiveEffects().iterator();
++
++ while (iterator.hasNext()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
++
++ this.connection.send(new ClientboundUpdateMobEffectPacket(vehicle.getId(), mobeffect));
+ }
+ }
+
+@@ -1783,26 +2322,165 @@
+
+ @Override
+ public void stopRiding() {
+- Entity vehicle = this.getVehicle();
++ Entity entity = this.getVehicle();
++
+ super.stopRiding();
+- if (vehicle instanceof LivingEntity livingEntity) {
+- for (MobEffectInstance mobEffectInstance : livingEntity.getActiveEffects()) {
+- this.connection.send(new ClientboundRemoveMobEffectPacket(vehicle.getId(), mobEffectInstance.getEffect()));
++ if (entity instanceof LivingEntity) {
++ LivingEntity entityliving = (LivingEntity) entity;
++ Iterator iterator = entityliving.getActiveEffects().iterator();
++
++ while (iterator.hasNext()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
++
++ this.connection.send(new ClientboundRemoveMobEffectPacket(entity.getId(), mobeffect.getEffect()));
+ }
+ }
++
+ }
+
+- public CommonPlayerSpawnInfo createCommonSpawnInfo(ServerLevel serverLevel) {
+- return new CommonPlayerSpawnInfo(
+- serverLevel.dimensionTypeId(),
+- serverLevel.dimension(),
+- BiomeManager.obfuscateSeed(serverLevel.getSeed()),
+- this.gameMode.getGameModeForPlayer(),
+- this.gameMode.getPreviousGameModeForPlayer(),
+- serverLevel.isDebug(),
+- serverLevel.isFlat(),
+- this.getLastDeathLocation(),
+- this.getPortalCooldown()
+- );
++ public CommonPlayerSpawnInfo createCommonSpawnInfo(ServerLevel worldserver) {
++ return new CommonPlayerSpawnInfo(worldserver.dimensionTypeId(), worldserver.dimension(), BiomeManager.obfuscateSeed(worldserver.getSeed()), this.gameMode.getGameModeForPlayer(), this.gameMode.getPreviousGameModeForPlayer(), worldserver.isDebug(), worldserver.isFlat(), this.getLastDeathLocation(), this.getPortalCooldown());
+ }
++
++ // CraftBukkit start - Add per-player time and weather.
++ public long timeOffset = 0;
++ public boolean relativeTime = true;
++
++ public long getPlayerTime() {
++ if (this.relativeTime) {
++ // Adds timeOffset to the current server time.
++ return this.level().getDayTime() + this.timeOffset;
++ } else {
++ // Adds timeOffset to the beginning of this day.
++ return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset;
++ }
++ }
++
++ public WeatherType weather = null;
++
++ public WeatherType getPlayerWeather() {
++ return this.weather;
++ }
++
++ public void setPlayerWeather(WeatherType type, boolean plugin) {
++ if (!plugin && this.weather != null) {
++ return;
++ }
++
++ if (plugin) {
++ this.weather = type;
++ }
++
++ if (type == WeatherType.DOWNFALL) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0));
++ } else {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0));
++ }
++ }
++
++ private float pluginRainPosition;
++ private float pluginRainPositionPrevious;
++
++ public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) {
++ if (this.weather == null) {
++ // Vanilla
++ if (oldRain != newRain) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain));
++ }
++ } else {
++ // Plugin
++ if (pluginRainPositionPrevious != pluginRainPosition) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, pluginRainPosition));
++ }
++ }
++
++ if (oldThunder != newThunder) {
++ if (weather == WeatherType.DOWNFALL || weather == null) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder));
++ } else {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0));
++ }
++ }
++ }
++
++ public void tickWeather() {
++ if (this.weather == null) return;
++
++ pluginRainPositionPrevious = pluginRainPosition;
++ if (weather == WeatherType.DOWNFALL) {
++ pluginRainPosition += 0.01;
++ } else {
++ pluginRainPosition -= 0.01;
++ }
++
++ pluginRainPosition = Mth.clamp(pluginRainPosition, 0.0F, 1.0F);
++ }
++
++ public void resetPlayerWeather() {
++ this.weather = null;
++ this.setPlayerWeather(this.level().getLevelData().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false);
++ }
++
++ @Override
++ public String toString() {
++ return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")";
++ }
++
++ // SPIGOT-1903, MC-98153
++ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) {
++ this.moveTo(x, y, z, yaw, pitch);
++ this.connection.resetPosition();
++ }
++
++ @Override
++ public boolean isImmobile() {
++ return super.isImmobile() || !getBukkitEntity().isOnline();
++ }
++
++ @Override
++ public Scoreboard getScoreboard() {
++ return getBukkitEntity().getScoreboard().getHandle();
++ }
++
++ public void reset() {
++ float exp = 0;
++ boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY);
++
++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++ exp = this.experienceProgress;
++ this.newTotalExp = this.totalExperience;
++ this.newLevel = this.experienceLevel;
++ }
++
++ this.setHealth(this.getMaxHealth());
++ this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset
++ this.setRemainingFireTicks(0);
++ this.fallDistance = 0;
++ this.foodData = new FoodData(this);
++ this.experienceLevel = this.newLevel;
++ this.totalExperience = this.newTotalExp;
++ this.experienceProgress = 0;
++ this.deathTime = 0;
++ this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent
++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
++ this.effectsDirty = true;
++ this.containerMenu = this.inventoryMenu;
++ this.lastHurtByPlayer = null;
++ this.lastHurtByMob = null;
++ this.combatTracker = new CombatTracker(this);
++ this.lastSentExp = -1;
++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++ this.experienceProgress = exp;
++ } else {
++ this.giveExperiencePoints(this.newExp);
++ }
++ this.keepLevel = false;
++ this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death
++ }
++
++ @Override
++ public CraftPlayer getBukkitEntity() {
++ return (CraftPlayer) super.getBukkitEntity();
++ }
++ // CraftBukkit end
+ }