All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.Player Maven / Gradle / Ivy

Go to download

A Minecraft Bedrock Edition server software implementation made in Java from scratch which supports all new features.

There is a newer version: 1.6.0.1-PN
Show newest version
package cn.nukkit;

import cn.nukkit.AdventureSettings.Type;
import cn.nukkit.api.PowerNukkitOnly;
import cn.nukkit.api.Since;
import cn.nukkit.block.*;
import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.blockentity.BlockEntityItemFrame;
import cn.nukkit.blockentity.BlockEntityLectern;
import cn.nukkit.blockentity.BlockEntitySpawnable;
import cn.nukkit.command.Command;
import cn.nukkit.command.CommandSender;
import cn.nukkit.command.data.CommandDataVersions;
import cn.nukkit.entity.*;
import cn.nukkit.entity.data.*;
import cn.nukkit.entity.item.*;
import cn.nukkit.entity.projectile.EntityArrow;
import cn.nukkit.entity.projectile.EntityThrownTrident;
import cn.nukkit.event.block.ItemFrameDropItemEvent;
import cn.nukkit.event.block.LecternPageChangeEvent;
import cn.nukkit.event.block.WaterFrostEvent;
import cn.nukkit.event.entity.*;
import cn.nukkit.event.entity.EntityDamageEvent.DamageCause;
import cn.nukkit.event.entity.EntityDamageEvent.DamageModifier;
import cn.nukkit.event.entity.EntityPortalEnterEvent.PortalType;
import cn.nukkit.event.inventory.InventoryCloseEvent;
import cn.nukkit.event.inventory.InventoryPickupArrowEvent;
import cn.nukkit.event.inventory.InventoryPickupItemEvent;
import cn.nukkit.event.inventory.InventoryPickupTridentEvent;
import cn.nukkit.event.player.*;
import cn.nukkit.event.player.PlayerAsyncPreLoginEvent.LoginResult;
import cn.nukkit.event.player.PlayerInteractEvent.Action;
import cn.nukkit.event.player.PlayerTeleportEvent.TeleportCause;
import cn.nukkit.event.server.DataPacketReceiveEvent;
import cn.nukkit.event.server.DataPacketSendEvent;
import cn.nukkit.form.window.FormWindow;
import cn.nukkit.form.window.FormWindowCustom;
import cn.nukkit.inventory.*;
import cn.nukkit.inventory.transaction.CraftingTransaction;
import cn.nukkit.inventory.transaction.EnchantTransaction;
import cn.nukkit.inventory.transaction.InventoryTransaction;
import cn.nukkit.inventory.transaction.action.InventoryAction;
import cn.nukkit.inventory.transaction.data.ReleaseItemData;
import cn.nukkit.inventory.transaction.data.UseItemData;
import cn.nukkit.inventory.transaction.data.UseItemOnEntityData;
import cn.nukkit.item.*;
import cn.nukkit.item.enchantment.Enchantment;
import cn.nukkit.lang.TextContainer;
import cn.nukkit.lang.TranslationContainer;
import cn.nukkit.level.*;
import cn.nukkit.level.format.FullChunk;
import cn.nukkit.level.format.generic.BaseFullChunk;
import cn.nukkit.level.particle.PunchBlockParticle;
import cn.nukkit.math.*;
import cn.nukkit.metadata.MetadataValue;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.*;
import cn.nukkit.network.Network;
import cn.nukkit.network.SourceInterface;
import cn.nukkit.network.protocol.*;
import cn.nukkit.network.protocol.types.ContainerIds;
import cn.nukkit.network.protocol.types.NetworkInventoryAction;
import cn.nukkit.permission.PermissibleBase;
import cn.nukkit.permission.Permission;
import cn.nukkit.permission.PermissionAttachment;
import cn.nukkit.permission.PermissionAttachmentInfo;
import cn.nukkit.plugin.Plugin;
import cn.nukkit.positiontracking.PositionTracking;
import cn.nukkit.positiontracking.PositionTrackingService;
import cn.nukkit.potion.Effect;
import cn.nukkit.resourcepacks.ResourcePack;
import cn.nukkit.scheduler.AsyncTask;
import cn.nukkit.scheduler.TaskHandler;
import cn.nukkit.utils.*;
import co.aikar.timings.Timing;
import co.aikar.timings.Timings;
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import io.netty.util.internal.EmptyArrays;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import lombok.extern.log4j.Log4j2;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteOrder;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
 * @author MagicDroidX & Box (Nukkit Project)
 */
@Log4j2
public class Player extends EntityHuman implements CommandSender, InventoryHolder, ChunkLoader, IPlayer {
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final Player[] EMPTY_ARRAY = new Player[0];
    
    private static final int NO_SHIELD_DELAY = 10;

    public static final int SURVIVAL = 0;
    public static final int CREATIVE = 1;
    public static final int ADVENTURE = 2;
    public static final int SPECTATOR = 3;
    public static final int VIEW = SPECTATOR;

    public static final int SURVIVAL_SLOTS = 36;
    public static final int CREATIVE_SLOTS = 112;

    public static final int CRAFTING_SMALL = 0;
    public static final int CRAFTING_BIG = 1;
    public static final int CRAFTING_ANVIL = 2;
    public static final int CRAFTING_ENCHANT = 3;
    public static final int CRAFTING_BEACON = 4;
    public static final @PowerNukkitOnly int CRAFTING_GRINDSTONE = 1000;
    public static final @PowerNukkitOnly int CRAFTING_STONECUTTER = 1001;
    public static final @PowerNukkitOnly int CRAFTING_CARTOGRAPHY = 1002;

    public static final float DEFAULT_SPEED = 0.1f;
    public static final float MAXIMUM_SPEED = 0.5f;

    public static final int PERMISSION_CUSTOM = 3;
    public static final int PERMISSION_OPERATOR = 2;
    public static final int PERMISSION_MEMBER = 1;
    public static final int PERMISSION_VISITOR = 0;

    public static final int ANVIL_WINDOW_ID = 2;
    public static final int ENCHANT_WINDOW_ID = 3;
    public static final int BEACON_WINDOW_ID = 4;
    public static final int GRINDSTONE_WINDOW_ID = 2;

    protected final SourceInterface interfaz;

    public boolean playedBefore;
    public boolean spawned = false;
    public boolean loggedIn = false;
    public boolean locallyInitialized = false;
    public int gamemode;
    public long lastBreak;
    private BlockVector3 lastBreakPosition = new BlockVector3();

    protected int windowCnt = 4;

    protected final BiMap windows = HashBiMap.create();

    protected final BiMap windowIndex = windows.inverse();
    protected final Set permanentWindows = new IntOpenHashSet();
    private boolean inventoryOpen;

    protected int messageCounter = 2;

    private String clientSecret;

    public Vector3 speed = null;

    public final HashSet achievements = new HashSet<>();

    public int craftingType = CRAFTING_SMALL;

    protected PlayerUIInventory playerUIInventory;
    protected CraftingGrid craftingGrid;
    protected CraftingTransaction craftingTransaction;
    @Since("1.3.1.0-PN") protected EnchantTransaction enchantTransaction;

    public long creationTime = 0;

    protected long randomClientId;

    protected Vector3 forceMovement = null;

    protected Vector3 teleportPosition = null;

    protected boolean connected = true;
    protected final InetSocketAddress socketAddress;
    protected boolean removeFormat = true;

    protected String username;
    protected String iusername;
    protected String displayName;

    protected int startAction = -1;

    protected Vector3 sleeping = null;
    protected Long clientID = null;

    private int loaderId;

    protected float stepHeight = 0.6f;

    public final Map usedChunks = new Long2ObjectOpenHashMap<>();

    protected int chunkLoadCount = 0;
    protected final Long2ObjectLinkedOpenHashMap loadQueue = new Long2ObjectLinkedOpenHashMap<>();
    protected int nextChunkOrderRun = 1;

    protected final Map hiddenPlayers = new HashMap<>();

    protected Vector3 newPosition = null;

    protected int chunkRadius;
    protected int viewDistance;
    protected final int chunksPerTick;
    protected final int spawnThreshold;

    protected Position spawnPosition = null;

    protected int inAirTicks = 0;
    protected int startAirTicks = 5;
    
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    private int noShieldTicks;

    protected AdventureSettings adventureSettings;

    protected boolean checkMovement = true;

    private final Queue packetQueue = new ConcurrentLinkedDeque<>();

    private PermissibleBase perm = null;

    private int exp = 0;
    private int expLevel = 0;

    protected PlayerFood foodData = null;

    private Entity killer = null;

    private final AtomicReference locale = new AtomicReference<>(null);

    private int hash;

    private String buttonText = "Button";

    protected boolean enableClientCommand = true;

    private BlockEnderChest viewingEnderChest = null;

    protected int lastEnderPearl = 20;
    protected int lastChorusFruitTeleport = 20;

    private LoginChainData loginChainData;

    public Block breakingBlock = null;

    public int pickedXPOrb = 0;

    protected int formWindowCount = 0;
    protected Map formWindows = new Int2ObjectOpenHashMap<>();
    protected Map serverSettings = new Int2ObjectOpenHashMap<>();

    protected Map dummyBossBars = new Long2ObjectLinkedOpenHashMap<>();

    private AsyncTask preLoginEventTask = null;
    protected boolean shouldLogin = false;

    public EntityFishingHook fishing = null;

    public long lastSkinChange;

    protected double lastRightClickTime = 0.0;
    protected Vector3 lastRightClickPos = null;

    protected int lastPlayerdLevelUpSoundTime = 0;
    
    private TaskHandler delayedPosTrackingUpdate;

    private float soulSpeedMultiplier = 1;
    private boolean wasInSoulSandCompatible;

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    private int timeSinceRest;

    public float getSoulSpeedMultiplier() {
        return this.soulSpeedMultiplier;
    }

    public int getStartActionTick() {
        return startAction;
    }

    public void startAction() {
        this.startAction = this.server.getTick();
    }

    public void stopAction() {
        this.startAction = -1;
    }

    public int getLastEnderPearlThrowingTick() {
        return lastEnderPearl;
    }

    public void onThrowEnderPearl() {
        this.lastEnderPearl = this.server.getTick();
    }

    public int getLastChorusFruitTeleport() {
        return lastChorusFruitTeleport;
    }

    public void onChorusFruitTeleport() {
        this.lastChorusFruitTeleport = this.server.getTick();
    }

    public BlockEnderChest getViewingEnderChest() {
        return viewingEnderChest;
    }

    public void setViewingEnderChest(BlockEnderChest chest) {
        if (chest == null && this.viewingEnderChest != null) {
            this.viewingEnderChest.getViewers().remove(this);
        } else if (chest != null) {
            chest.getViewers().add(this);
        }
        this.viewingEnderChest = chest;
    }

    public TranslationContainer getLeaveMessage() {
        return new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.left", this.getDisplayName());
    }

    public String getClientSecret() {
        return clientSecret;
    }

    /**
     * This might disappear in the future.
     * Please use getUniqueId() instead (IP + clientId + name combo, in the future it'll change to real UUID for online auth)
     * @return random client id
     */
    @Deprecated
    public Long getClientId() {
        return randomClientId;
    }

    @Override
    public boolean isBanned() {
        return this.server.getNameBans().isBanned(this.getName());
    }

    @Override
    public void setBanned(boolean value) {
        if (value) {
            this.server.getNameBans().addBan(this.getName(), null, null, null);
            this.kick(PlayerKickEvent.Reason.NAME_BANNED, "Banned by admin");
        } else {
            this.server.getNameBans().remove(this.getName());
        }
    }

    @Override
    public boolean isWhitelisted() {
        return this.server.isWhitelisted(this.getName().toLowerCase());
    }

    @Override
    public void setWhitelisted(boolean value) {
        if (value) {
            this.server.addWhitelist(this.getName().toLowerCase());
        } else {
            this.server.removeWhitelist(this.getName().toLowerCase());
        }
    }

    @Override
    public Player getPlayer() {
        return this;
    }

    @Override
    public Long getFirstPlayed() {
        return this.namedTag != null ? this.namedTag.getLong("firstPlayed") : null;
    }

    @Override
    public Long getLastPlayed() {
        return this.namedTag != null ? this.namedTag.getLong("lastPlayed") : null;
    }

    @Override
    public boolean hasPlayedBefore() {
        return this.playedBefore;
    }

    public AdventureSettings getAdventureSettings() {
        return adventureSettings;
    }

    public void setAdventureSettings(AdventureSettings adventureSettings) {
        this.adventureSettings = adventureSettings.clone(this);
        this.adventureSettings.update();
    }

    public void resetInAirTicks() {
        this.inAirTicks = 0;
    }

    @Deprecated
    public void setAllowFlight(boolean value) {
        this.getAdventureSettings().set(Type.ALLOW_FLIGHT, value);
        this.getAdventureSettings().update();
    }

    @Deprecated
    public boolean getAllowFlight() {
        return this.getAdventureSettings().get(Type.ALLOW_FLIGHT);
    }

    public void setAllowModifyWorld(boolean value) {
        this.getAdventureSettings().set(Type.WORLD_IMMUTABLE, !value);
        this.getAdventureSettings().set(Type.BUILD_AND_MINE, value);
        this.getAdventureSettings().set(Type.WORLD_BUILDER, value);
        this.getAdventureSettings().update();
    }

    public void setAllowInteract(boolean value) {
        setAllowInteract(value, value);
    }

    public void setAllowInteract(boolean value, boolean containers) {
        this.getAdventureSettings().set(Type.WORLD_IMMUTABLE, !value);
        this.getAdventureSettings().set(Type.DOORS_AND_SWITCHED, value);
        this.getAdventureSettings().set(Type.OPEN_CONTAINERS, containers);
        this.getAdventureSettings().update();
    }

    @Deprecated
    public void setAutoJump(boolean value) {
        this.getAdventureSettings().set(Type.AUTO_JUMP, value);
        this.getAdventureSettings().update();
    }

    @Deprecated
    public boolean hasAutoJump() {
        return this.getAdventureSettings().get(Type.AUTO_JUMP);
    }

    @Override
    public void spawnTo(Player player) {
        if (this.spawned && player.spawned && this.isAlive() && player.getLevel() == this.level && player.canSee(this) && !this.isSpectator()) {
            super.spawnTo(player);
        }
    }

    @Override
    public Server getServer() {
        return this.server;
    }

    public boolean getRemoveFormat() {
        return removeFormat;
    }

    public void setRemoveFormat() {
        this.setRemoveFormat(true);
    }

    public void setRemoveFormat(boolean remove) {
        this.removeFormat = remove;
    }

    public boolean canSee(Player player) {
        return !this.hiddenPlayers.containsKey(player.getUniqueId());
    }

    public void hidePlayer(Player player) {
        if (this == player) {
            return;
        }
        this.hiddenPlayers.put(player.getUniqueId(), player);
        player.despawnFrom(this);
    }

    public void showPlayer(Player player) {
        if (this == player) {
            return;
        }
        this.hiddenPlayers.remove(player.getUniqueId());
        if (player.isOnline()) {
            player.spawnTo(this);
        }
    }

    @Override
    public boolean canCollideWith(Entity entity) {
        return false;
    }

    @Override
    public void resetFallDistance() {
        super.resetFallDistance();
        if (this.inAirTicks != 0) {
            this.startAirTicks = 5;
        }
        this.inAirTicks = 0;
        this.highestPosition = this.y;
    }

    @Override
    public boolean isOnline() {
        return this.connected && this.loggedIn;
    }

    @Override
    public boolean isOp() {
        return this.server.isOp(this.getName());
    }

    @Override
    public void setOp(boolean value) {
        if (value == this.isOp()) {
            return;
        }

        if (value) {
            this.server.addOp(this.getName());
        } else {
            this.server.removeOp(this.getName());
        }

        this.recalculatePermissions();
        this.getAdventureSettings().update();
        this.sendCommandData();
    }

    @Override
    public boolean isPermissionSet(String name) {
        return this.perm.isPermissionSet(name);
    }

    @Override
    public boolean isPermissionSet(Permission permission) {
        return this.perm.isPermissionSet(permission);
    }

    @Override
    public boolean hasPermission(String name) {
        return this.perm != null && this.perm.hasPermission(name);
    }

    @Override
    public boolean hasPermission(Permission permission) {
        return this.perm.hasPermission(permission);
    }

    @Override
    public PermissionAttachment addAttachment(Plugin plugin) {
        return this.addAttachment(plugin, null);
    }

    @Override
    public PermissionAttachment addAttachment(Plugin plugin, String name) {
        return this.addAttachment(plugin, name, null);
    }

    @Override
    public PermissionAttachment addAttachment(Plugin plugin, String name, Boolean value) {
        return this.perm.addAttachment(plugin, name, value);
    }

    @Override
    public void removeAttachment(PermissionAttachment attachment) {
        this.perm.removeAttachment(attachment);
    }

    @Override
    public void recalculatePermissions() {
        this.server.getPluginManager().unsubscribeFromPermission(Server.BROADCAST_CHANNEL_USERS, this);
        this.server.getPluginManager().unsubscribeFromPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this);

        if (this.perm == null) {
            return;
        }

        this.perm.recalculatePermissions();

        if (this.hasPermission(Server.BROADCAST_CHANNEL_USERS)) {
            this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_USERS, this);
        }

        if (this.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE)) {
            this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this);
        }

        if (this.isEnableClientCommand() && spawned) this.sendCommandData();
    }

    public boolean isEnableClientCommand() {
        return this.enableClientCommand;
    }

    public void setEnableClientCommand(boolean enable) {
        this.enableClientCommand = enable;
        SetCommandsEnabledPacket pk = new SetCommandsEnabledPacket();
        pk.enabled = enable;
        this.dataPacket(pk);
        if (enable) this.sendCommandData();
    }

    public void sendCommandData() {
        if (!spawned) {
            return;
        }
        AvailableCommandsPacket pk = new AvailableCommandsPacket();
        Map data = new HashMap<>();
        int count = 0;
        for (Command command : this.server.getCommandMap().getCommands().values()) {
            if (!command.testPermissionSilent(this)) {
                continue;
            }
            ++count;
            CommandDataVersions data0 = command.generateCustomCommandData(this);
            data.put(command.getName(), data0);
        }
        if (count > 0) {
            //TODO: structure checking
            pk.commands = data;
            this.dataPacket(pk);
        }
    }

    @Override
    public Map getEffectivePermissions() {
        return this.perm.getEffectivePermissions();
    }
    
    private static InetSocketAddress uncheckedNewInetSocketAddress(String ip, int port) {
        try {
            return new InetSocketAddress(InetAddress.getByName(ip), port);
        } catch (UnknownHostException exception) {
            throw new IllegalArgumentException(exception);
        }
    }
    
    public Player(SourceInterface interfaz, Long clientID, String ip, int port) {
        this(interfaz, clientID, uncheckedNewInetSocketAddress(ip, port));
    }
    
    @PowerNukkitOnly
    public Player(SourceInterface interfaz, Long clientID, InetSocketAddress socketAddress) {
        super(null, new CompoundTag());
        this.interfaz = interfaz;
        this.perm = new PermissibleBase(this);
        this.server = Server.getInstance();
        this.lastBreak = -1;
        this.socketAddress = socketAddress;
        this.clientID = clientID;
        this.loaderId = Level.generateChunkLoaderId(this);
        this.chunksPerTick = this.server.getConfig("chunk-sending.per-tick", 4);
        this.spawnThreshold = this.server.getConfig("chunk-sending.spawn-threshold", 56);
        this.spawnPosition = null;
        this.gamemode = this.server.getGamemode();
        this.setLevel(this.server.getDefaultLevel());
        this.viewDistance = this.server.getViewDistance();
        this.chunkRadius = viewDistance;
        //this.newPosition = new Vector3(0, 0, 0);
        this.boundingBox = new SimpleAxisAlignedBB(0, 0, 0, 0, 0, 0);
        this.lastSkinChange = -1;

        this.uuid = null;
        this.rawUUID = null;

        this.creationTime = System.currentTimeMillis();
    }

    @Override
    protected void initEntity() {
        super.initEntity();

        this.addDefaultWindows();
    }

    public boolean isPlayer() {
        return true;
    }

    public void removeAchievement(String achievementId) {
        achievements.remove(achievementId);
    }

    public boolean hasAchievement(String achievementId) {
        return achievements.contains(achievementId);
    }

    public boolean isConnected() {
        return connected;
    }

    public String getDisplayName() {
        return this.displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
        if (this.spawned) {
            this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getDisplayName(), this.getSkin(), this.getLoginChainData().getXUID());
        }
    }

    @Override
    public void setSkin(Skin skin) {
        super.setSkin(skin);
        if (this.spawned) {
            this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getDisplayName(), skin, this.getLoginChainData().getXUID());
        }
    }

    public String getAddress() {
        return this.socketAddress.getAddress().getHostAddress();
    }

    public int getPort() {
        return this.socketAddress.getPort();
    }

    public InetSocketAddress getSocketAddress() {
        return this.socketAddress;
    }

    public Position getNextPosition() {
        return this.newPosition != null ? new Position(this.newPosition.x, this.newPosition.y, this.newPosition.z, this.level) : this.getPosition();
    }

    public boolean isSleeping() {
        return this.sleeping != null;
    }

    public int getInAirTicks() {
        return this.inAirTicks;
    }

    /**
     * Returns whether the player is currently using an item (right-click and hold).
     *
     * @return bool
     */
    public boolean isUsingItem() {
        return this.getDataFlag(DATA_FLAGS, DATA_FLAG_ACTION) && this.startAction > -1;
    }

    public void setUsingItem(boolean value) {
        this.startAction = value ? this.server.getTick() : -1;
        this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, value);
    }

    public String getButtonText() {
        return this.buttonText;
    }

    public void setButtonText(String text) {
        this.buttonText = text;
        this.setDataProperty(new StringEntityData(Entity.DATA_INTERACTIVE_TAG, this.buttonText));
    }

    public void unloadChunk(int x, int z) {
        this.unloadChunk(x, z, null);
    }

    public void unloadChunk(int x, int z, Level level) {
        level = level == null ? this.level : level;
        long index = Level.chunkHash(x, z);
        if (this.usedChunks.containsKey(index)) {
            for (Entity entity : level.getChunkEntities(x, z).values()) {
                if (entity != this) {
                    entity.despawnFrom(this);
                }
            }

            this.usedChunks.remove(index);
        }
        level.unregisterChunkLoader(this, x, z);
        this.loadQueue.remove(index);
    }

    public Position getSpawn() {
        if (this.spawnPosition != null && this.spawnPosition.getLevel() != null) {
            return this.spawnPosition;
        } else {
            return this.server.getDefaultLevel().getSafeSpawn();
        }
    }

    public void sendChunk(int x, int z, DataPacket packet) {
        if (!this.connected) {
            return;
        }

        this.usedChunks.put(Level.chunkHash(x, z), Boolean.TRUE);
        this.chunkLoadCount++;

        this.dataPacket(packet);

        if (this.spawned) {
            for (Entity entity : this.level.getChunkEntities(x, z).values()) {
                if (this != entity && !entity.closed && entity.isAlive()) {
                    entity.spawnTo(this);
                }
            }
        }
    }

    public void sendChunk(int x, int z, int subChunkCount, byte[] payload) {
        if (!this.connected) {
            return;
        }

        this.usedChunks.put(Level.chunkHash(x, z), true);
        this.chunkLoadCount++;

        LevelChunkPacket pk = new LevelChunkPacket();
        pk.chunkX = x;
        pk.chunkZ = z;
        pk.subChunkCount = subChunkCount;
        pk.data = payload;

        this.batchDataPacket(pk);

        if (this.spawned) {
            for (Entity entity : this.level.getChunkEntities(x, z).values()) {
                if (this != entity && !entity.closed && entity.isAlive()) {
                    entity.spawnTo(this);
                }
            }
        }
    }

    protected void sendNextChunk() {
        if (!this.connected) {
            return;
        }

        Timings.playerChunkSendTimer.startTiming();

        if (!loadQueue.isEmpty()) {
            int count = 0;
            ObjectIterator> iter = loadQueue.long2ObjectEntrySet().fastIterator();
            while (iter.hasNext()) {
                Long2ObjectMap.Entry entry = iter.next();
                long index = entry.getLongKey();

                if (count >= this.chunksPerTick) {
                    break;
                }
                int chunkX = Level.getHashX(index);
                int chunkZ = Level.getHashZ(index);

                ++count;

                this.usedChunks.put(index, false);
                this.level.registerChunkLoader(this, chunkX, chunkZ, false);

                if (!this.level.populateChunk(chunkX, chunkZ)) {
                    if (this.spawned && this.teleportPosition == null) {
                        continue;
                    } else {
                        break;
                    }
                }

                iter.remove();

                PlayerChunkRequestEvent ev = new PlayerChunkRequestEvent(this, chunkX, chunkZ);
                this.server.getPluginManager().callEvent(ev);
                if (!ev.isCancelled()) {
                    this.level.requestChunk(chunkX, chunkZ, this);
                }
            }
        }
        if (this.chunkLoadCount >= this.spawnThreshold && !this.spawned && this.teleportPosition == null) {
            this.doFirstSpawn();
        }
        Timings.playerChunkSendTimer.stopTiming();
    }

    protected void doFirstSpawn() {
        this.spawned = true;

        this.inventory.sendContents(this);
        this.inventory.sendHeldItem(this);
        this.inventory.sendArmorContents(this);
        this.offhandInventory.sendContents(this);
        this.setEnableClientCommand(true);

        SetTimePacket setTimePacket = new SetTimePacket();
        setTimePacket.time = this.level.getTime();
        this.dataPacket(setTimePacket);

        Location pos;
        if(this.server.isSafeSpawn()) {
            pos = this.level.getSafeSpawn(this).getLocation();
            pos.yaw = this.yaw;
            pos.pitch = this.pitch;
        } else {
            pos = new Location(this.forceMovement.x,this.forceMovement.y,this.forceMovement.z, this.yaw, this.pitch, this.level);
        }


        PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this, pos, true);

        this.server.getPluginManager().callEvent(respawnEvent);

        Position fromEvent = respawnEvent.getRespawnPosition();
        if (fromEvent instanceof Location) {
            pos = fromEvent.getLocation();
        } else {
            pos = fromEvent.getLocation();
            pos.yaw = this.yaw;
            pos.pitch = this.pitch;
        }

        this.teleport(pos, null);
        lastYaw = yaw;
        lastPitch = pitch;

        this.sendPlayStatus(PlayStatusPacket.PLAYER_SPAWN);

        PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(this,
                new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.joined", new String[]{
                        this.getDisplayName()
                })
        );

        this.server.getPluginManager().callEvent(playerJoinEvent);

        if (playerJoinEvent.getJoinMessage().toString().trim().length() > 0) {
            this.server.broadcastMessage(playerJoinEvent.getJoinMessage());
        }

        this.noDamageTicks = 60;

        this.getServer().sendRecipeList(this);


        for (long index : this.usedChunks.keySet()) {
            int chunkX = Level.getHashX(index);
            int chunkZ = Level.getHashZ(index);
            for (Entity entity : this.level.getChunkEntities(chunkX, chunkZ).values()) {
                if (this != entity && !entity.closed && entity.isAlive()) {
                    entity.spawnTo(this);
                }
            }
        }

        int experience = this.getExperience();
        if (experience != 0) {
            this.sendExperience(experience);
        }

        int level = this.getExperienceLevel();
        if (level != 0) {
            this.sendExperienceLevel(this.getExperienceLevel());
        }

        if (!this.isSpectator()) {
            this.spawnToAll();
        }

        //todo Updater

        //Weather
        this.getLevel().sendWeather(this);

        //FoodLevel
        PlayerFood food = this.getFoodData();
        if (food.getLevel() != food.getMaxLevel()) {
            food.sendFoodLevel();
        }

        if (this.getHealth() < 1) {
            this.respawn();
        } else {
            updateTrackingPositions(false);
        }
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void updateTrackingPositions() {
        updateTrackingPositions(false);
    }
    
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void updateTrackingPositions(boolean delayed) {
        Server server = getServer();
        if (delayed) {
            if (delayedPosTrackingUpdate != null) {
                delayedPosTrackingUpdate.cancel();
            }
            delayedPosTrackingUpdate = server.getScheduler().scheduleDelayedTask(null, this::updateTrackingPositions, 10);
            return;
        }
        PositionTrackingService positionTrackingService = server.getPositionTrackingService();
        positionTrackingService.forceRecheck(this);
    }

    protected boolean orderChunks() {
        if (!this.connected) {
            return false;
        }

        Timings.playerChunkOrderTimer.startTiming();

        this.nextChunkOrderRun = 200;

        loadQueue.clear();
        Long2ObjectOpenHashMap lastChunk = new Long2ObjectOpenHashMap<>(this.usedChunks);

        int centerX = (int) this.x >> 4;
        int centerZ = (int) this.z >> 4;

        int radius = spawned ? this.chunkRadius : (int) Math.ceil(Math.sqrt(spawnThreshold));
        int radiusSqr = radius * radius;



        long index;
        for (int x = 0; x <= radius; x++) {
            int xx = x * x;
            for (int z = 0; z <= x; z++) {
                int distanceSqr = xx + z * z;
                if (distanceSqr > radiusSqr) continue;

                /* Top right quadrant */
                if(this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ + z)) != Boolean.TRUE) {
                    this.loadQueue.put(index, Boolean.TRUE);
                }
                lastChunk.remove(index);
                /* Top left quadrant */
                if(this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ + z)) != Boolean.TRUE) {
                    this.loadQueue.put(index, Boolean.TRUE);
                }
                lastChunk.remove(index);
                /* Bottom right quadrant */
                if(this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ - z - 1)) != Boolean.TRUE) {
                    this.loadQueue.put(index, Boolean.TRUE);
                }
                lastChunk.remove(index);
                /* Bottom left quadrant */
                if(this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ - z - 1)) != Boolean.TRUE) {
                    this.loadQueue.put(index, Boolean.TRUE);
                }
                lastChunk.remove(index);
                if(x != z){
                    /* Top right quadrant mirror */
                    if(this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ + x)) != Boolean.TRUE) {
                        this.loadQueue.put(index, Boolean.TRUE);
                    }
                    lastChunk.remove(index);
                    /* Top left quadrant mirror */
                    if(this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ + x)) != Boolean.TRUE) {
                        this.loadQueue.put(index, Boolean.TRUE);
                    }
                    lastChunk.remove(index);
                    /* Bottom right quadrant mirror */
                    if(this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ - x - 1)) != Boolean.TRUE) {
                        this.loadQueue.put(index, Boolean.TRUE);
                    }
                    lastChunk.remove(index);
                    /* Bottom left quadrant mirror */
                    if(this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ - x - 1)) != Boolean.TRUE) {
                        this.loadQueue.put(index, Boolean.TRUE);
                    }
                    lastChunk.remove(index);
                }
            }
        }

        LongIterator keys = lastChunk.keySet().iterator();
        while (keys.hasNext()) {
            index = keys.nextLong();
            this.unloadChunk(Level.getHashX(index), Level.getHashZ(index));
        }

        if (!loadQueue.isEmpty()) {
            NetworkChunkPublisherUpdatePacket packet = new NetworkChunkPublisherUpdatePacket();
            packet.position = this.asBlockVector3();
            packet.radius = viewDistance << 4;
            this.dataPacket(packet);
        }

        Timings.playerChunkOrderTimer.stopTiming();
        return true;
    }

    public boolean batchDataPacket(DataPacket packet) {
        if (packet instanceof BatchPacket) {
            this.directDataPacket(packet); // We don't want to batch a batched packet...
        }
        if (!this.connected) {
            return false;
        }

        try (Timing timing = Timings.getSendDataPacketTiming(packet)) {
            DataPacketSendEvent event = new DataPacketSendEvent(this, packet);
            this.server.getPluginManager().callEvent(event);
            if (event.isCancelled()) {
                return false;
            }
            if (log.isTraceEnabled() && !server.isIgnoredPacket(packet.getClass())) {
                log.trace("Outbound {}: {}", this.getName(), packet);
            }

            this.packetQueue.offer(packet);
        }
        return true;
    }

    /**
     * 0 is true
     * -1 is false
     * other is identifer
     * @param packet packet to send
     * @return packet successfully sent
     */
    public boolean dataPacket(DataPacket packet) {
        return batchDataPacket(packet);
    }

    @Deprecated
    public int dataPacket(DataPacket packet, boolean needACK) {
        return this.dataPacket(packet) ? 0 : -1;
    }

    /**
     * 0 is true
     * -1 is false
     * other is identifer
     * @param packet packet to send
     * @return packet successfully sent
     */
    public boolean directDataPacket(DataPacket packet) {
        if (!this.connected) {
            return false;
        }

        try (Timing timing = Timings.getSendDataPacketTiming(packet)) {
            DataPacketSendEvent ev = new DataPacketSendEvent(this, packet);
            this.server.getPluginManager().callEvent(ev);
            if (ev.isCancelled()) {
                return false;
            }

            this.interfaz.putPacket(this, packet, false, true);
        }
        return true;
    }

    @Deprecated
    public int directDataPacket(DataPacket packet, boolean needACK) {
        return this.directDataPacket(packet) ? 0 : -1;
    }

    public int getPing() {
        return this.interfaz.getNetworkLatency(this);
    }

    public boolean sleepOn(Vector3 pos) {
        if (!this.isOnline()) {
            return false;
        }

        for (Entity p : this.level.getNearbyEntities(this.boundingBox.grow(2, 1, 2), this)) {
            if (p instanceof Player) {
                if (((Player) p).sleeping != null && pos.distance(((Player) p).sleeping) <= 0.1) {
                    return false;
                }
            }
        }

        PlayerBedEnterEvent ev;
        this.server.getPluginManager().callEvent(ev = new PlayerBedEnterEvent(this, this.level.getBlock(pos)));
        if (ev.isCancelled()) {
            return false;
        }

        this.sleeping = pos.clone();
        this.teleport(new Location(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5, this.yaw, this.pitch, this.level), null);

        this.setDataProperty(new IntPositionEntityData(DATA_PLAYER_BED_POSITION, (int) pos.x, (int) pos.y, (int) pos.z));
        this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, true);

        this.setSpawn(pos);

        this.level.sleepTicks = 60;

        this.timeSinceRest = 0;

        return true;
    }

    public void setSpawn(Vector3 pos) {
        Level level;
        if (!(pos instanceof Position)) {
            level = this.level;
        } else {
            level = ((Position) pos).getLevel();
        }
        this.spawnPosition = new Position(pos.x, pos.y, pos.z, level);
        SetSpawnPositionPacket pk = new SetSpawnPositionPacket();
        pk.spawnType = SetSpawnPositionPacket.TYPE_PLAYER_SPAWN;
        pk.x = (int) this.spawnPosition.x;
        pk.y = (int) this.spawnPosition.y;
        pk.z = (int) this.spawnPosition.z;
        pk.dimension = this.getLevel().getDimension();
        this.dataPacket(pk);
    }

    public void stopSleep() {
        if (this.sleeping != null) {
            this.server.getPluginManager().callEvent(new PlayerBedLeaveEvent(this, this.level.getBlock(this.sleeping)));

            this.sleeping = null;
            this.setDataProperty(new IntPositionEntityData(DATA_PLAYER_BED_POSITION, 0, 0, 0));
            this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, false);


            this.level.sleepTicks = 0;

            AnimatePacket pk = new AnimatePacket();
            pk.eid = this.id;
            pk.action = AnimatePacket.Action.WAKE_UP;
            this.dataPacket(pk);
        }
    }

    public boolean awardAchievement(String achievementId) {
        if (!Server.getInstance().getPropertyBoolean("achievements", true)) {
            return false;
        }

        Achievement achievement = Achievement.achievements.get(achievementId);

        if (achievement == null || hasAchievement(achievementId)) {
            return false;
        }

        for (String id : achievement.requires) {
            if (!this.hasAchievement(id)) {
                return false;
            }
        }
        PlayerAchievementAwardedEvent event = new PlayerAchievementAwardedEvent(this, achievementId);
        this.server.getPluginManager().callEvent(event);

        if (event.isCancelled()) {
            return false;
        }

        this.achievements.add(achievementId);
        achievement.broadcast(this);
        return true;
    }

    public int getGamemode() {
        return gamemode;
    }

    /**
     * Returns a client-friendly gamemode of the specified real gamemode
     * This function takes care of handling gamemodes known to MCPE (as of 1.1.0.3, that includes Survival, Creative and Adventure)
     * 

* TODO: remove this when Spectator Mode gets added properly to MCPE */ private static int getClientFriendlyGamemode(int gamemode) { gamemode &= 0x03; if (gamemode == Player.SPECTATOR) { return Player.CREATIVE; } return gamemode; } public boolean setGamemode(int gamemode) { return this.setGamemode(gamemode, false, null); } public boolean setGamemode(int gamemode, boolean clientSide) { return this.setGamemode(gamemode, clientSide, null); } public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings newSettings) { if (gamemode < 0 || gamemode > 3 || this.gamemode == gamemode) { return false; } if (newSettings == null) { newSettings = this.getAdventureSettings().clone(this); newSettings.set(Type.WORLD_IMMUTABLE, (gamemode & 0x02) > 0); newSettings.set(Type.BUILD_AND_MINE, (gamemode & 0x02) <= 0); newSettings.set(Type.WORLD_BUILDER, (gamemode & 0x02) <= 0); newSettings.set(Type.ALLOW_FLIGHT, (gamemode & 0x01) > 0); newSettings.set(Type.NO_CLIP, gamemode == 0x03); newSettings.set(Type.FLYING, gamemode == 0x03); } PlayerGameModeChangeEvent ev; this.server.getPluginManager().callEvent(ev = new PlayerGameModeChangeEvent(this, gamemode, newSettings)); if (ev.isCancelled()) { return false; } this.gamemode = gamemode; if (this.isSpectator()) { this.keepMovement = true; this.despawnFromAll(); } else { this.keepMovement = false; this.spawnToAll(); } this.namedTag.putInt("playerGameType", this.gamemode); if (!clientSide) { SetPlayerGameTypePacket pk = new SetPlayerGameTypePacket(); pk.gamemode = getClientFriendlyGamemode(gamemode); this.dataPacket(pk); } this.setAdventureSettings(ev.getNewAdventureSettings()); if (this.isSpectator()) { this.getAdventureSettings().set(Type.FLYING, true); this.teleport(this.temporalVector.setComponents(this.x, this.y + 0.1, this.z)); /*InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); inventoryContentPacket.inventoryId = InventoryContentPacket.SPECIAL_CREATIVE; this.dataPacket(inventoryContentPacket);*/ } else { if (this.isSurvival()) { this.getAdventureSettings().set(Type.FLYING, false); } /*InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); inventoryContentPacket.inventoryId = InventoryContentPacket.SPECIAL_CREATIVE; inventoryContentPacket.slots = Item.getCreativeItems().toArray(new Item[0]); this.dataPacket(inventoryContentPacket);*/ } this.resetFallDistance(); this.inventory.sendContents(this); this.inventory.sendContents(this.getViewers().values()); this.inventory.sendHeldItem(this.hasSpawned.values()); this.offhandInventory.sendContents(this); this.offhandInventory.sendContents(this.getViewers().values()); this.inventory.sendCreativeContents(); return true; } @Deprecated public void sendSettings() { this.getAdventureSettings().update(); } public boolean isSurvival() { return this.gamemode == SURVIVAL; } public boolean isCreative() { return this.gamemode == CREATIVE; } public boolean isSpectator() { return this.gamemode == SPECTATOR; } public boolean isAdventure() { return this.gamemode == ADVENTURE; } @Override public Item[] getDrops() { if (!this.isCreative() && !this.isSpectator()) { return super.getDrops(); } return Item.EMPTY_ARRAY; } @Override public boolean setDataProperty(EntityData data) { return setDataProperty(data, true); } @Override public boolean setDataProperty(EntityData data, boolean send) { if (super.setDataProperty(data, send)) { if (send) this.sendData(this, new EntityMetadata().put(this.getDataProperty(data.getId()))); return true; } return false; } @Override protected void checkGroundState(double movX, double movY, double movZ, double dx, double dy, double dz) { if (!this.onGround || movX != 0 || movY != 0 || movZ != 0) { boolean onGround = false; AxisAlignedBB bb = this.boundingBox.clone(); bb.setMaxY(bb.getMinY() + 0.5); bb.setMinY(bb.getMinY() - 1); AxisAlignedBB realBB = this.boundingBox.clone(); realBB.setMaxY(realBB.getMinY() + 0.1); realBB.setMinY(realBB.getMinY() - 0.2); int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); int maxX = NukkitMath.ceilDouble(bb.getMaxX()); int maxY = NukkitMath.ceilDouble(bb.getMaxY()); int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.level.getBlock(this.temporalVector.setComponents(x, y, z)); if (!block.canPassThrough() && block.collidesWithBB(realBB)) { onGround = true; break; } } } } this.onGround = onGround; } this.isCollided = this.onGround; } @Override protected void checkBlockCollision() { boolean portal = false; boolean scaffolding = false; boolean endPortal = false; for (Block block : this.getCollisionBlocks()) { switch (block.getId()) { case BlockID.NETHER_PORTAL: portal = true; break; case BlockID.SCAFFOLDING: scaffolding = true; break; case BlockID.END_PORTAL: endPortal = true; break; } block.onEntityCollide(this); block.getLevelBlockAtLayer(1).onEntityCollide(this); } setDataFlag(DATA_FLAGS_EXTENDED, DATA_FLAG_IN_SCAFFOLDING, scaffolding); AxisAlignedBB scanBoundingBox = boundingBox.getOffsetBoundingBox(0, -0.125, 0); scanBoundingBox.setMaxY(boundingBox.getMinY()); Block[] scaffoldingUnder = level.getCollisionBlocks( scanBoundingBox, true, true, b-> b.getId() == BlockID.SCAFFOLDING ); setDataFlag(DATA_FLAGS_EXTENDED, DATA_FLAG_OVER_SCAFFOLDING, scaffoldingUnder.length > 0); if (endPortal) { if (!inEndPortal) { inEndPortal = true; EntityPortalEnterEvent ev = new EntityPortalEnterEvent(this, PortalType.END); getServer().getPluginManager().callEvent(ev); } } else { inEndPortal = false; } if (portal) { if (this.isCreative() && this.inPortalTicks < 80) { this.inPortalTicks = 80; } else { this.inPortalTicks++; } } else { this.inPortalTicks = 0; } } protected void checkNearEntities() { for (Entity entity : this.level.getNearbyEntities(this.boundingBox.grow(1, 0.5, 1), this)) { entity.scheduleUpdate(); if (!entity.isAlive() || !this.isAlive()) { continue; } this.pickupEntity(entity, true); } } protected void processMovement(int tickDiff) { if (!this.isAlive() || !this.spawned || this.newPosition == null || this.teleportPosition != null || this.isSleeping()) { this.positionChanged = false; return; } Vector3 newPos = this.newPosition; double distanceSquared = newPos.distanceSquared(this); boolean revert = false; if ((distanceSquared / ((double) (tickDiff * tickDiff))) > 100 && (newPos.y - this.y) > -5) { revert = true; } else { if (this.chunk == null || !this.chunk.isGenerated()) { BaseFullChunk chunk = this.level.getChunk((int) newPos.x >> 4, (int) newPos.z >> 4, false); if (chunk == null || !chunk.isGenerated()) { revert = true; this.nextChunkOrderRun = 0; } else { if (this.chunk != null) { this.chunk.removeEntity(this); } this.chunk = chunk; } } } double tdx = newPos.x - this.x; double tdz = newPos.z - this.z; double distance = Math.sqrt(tdx * tdx + tdz * tdz); if (!revert && distanceSquared != 0) { double dx = newPos.x - this.x; double dy = newPos.y - this.y; double dz = newPos.z - this.z; this.fastMove(dx, dy, dz); if (this.newPosition == null) { return; //maybe solve that in better way } double diffX = this.x - newPos.x; double diffY = this.y - newPos.y; double diffZ = this.z - newPos.z; double yS = 0.5 + this.ySize; if (diffY >= -yS || diffY <= yS) { diffY = 0; } if (diffX != 0 || diffY != 0 || diffZ != 0) { if (this.checkMovement && !isOp() && !server.getAllowFlight() && (this.isSurvival() || this.isAdventure())) { // Some say: I cant move my head when riding because the server // blocked my movement if (!this.isSleeping() && this.riding == null && !this.hasEffect(Effect.LEVITATION)) { double diffHorizontalSqr = (diffX * diffX + diffZ * diffZ) / ((double) (tickDiff * tickDiff)); if (diffHorizontalSqr > 0.5) { PlayerInvalidMoveEvent ev; this.getServer().getPluginManager().callEvent(ev = new PlayerInvalidMoveEvent(this, true)); if (!ev.isCancelled()) { revert = ev.isRevert(); if (revert) { this.server.getLogger().warning(this.getServer().getLanguage().translateString("nukkit.player.invalidMove", this.getName())); } } } } } this.x = newPos.x; this.y = newPos.y; this.z = newPos.z; double radius = this.getWidth() / 2; this.boundingBox.setBounds(this.x - radius, this.y, this.z - radius, this.x + radius, this.y + this.getHeight(), this.z + radius); } } Location from = new Location( this.lastX, this.lastY, this.lastZ, this.lastYaw, this.lastPitch, this.level); Location to = this.getLocation(); double delta = Math.pow(this.lastX - to.x, 2) + Math.pow(this.lastY - to.y, 2) + Math.pow(this.z - to.z, 2); double deltaAngle = Math.abs(this.lastYaw - to.yaw) + Math.abs(this.lastPitch - to.pitch); if (!revert && (delta > 0.0001d || deltaAngle > 1d)) { boolean isFirst = this.firstMove; this.firstMove = false; this.lastX = to.x; this.lastY = to.y; this.lastZ = to.z; this.lastYaw = to.yaw; this.lastPitch = to.pitch; if (!isFirst) { List blocksAround = new ArrayList<>(this.blocksAround); List collidingBlocks = new ArrayList<>(this.collisionBlocks); PlayerMoveEvent ev = new PlayerMoveEvent(this, from, to); this.blocksAround = null; this.collisionBlocks = null; this.server.getPluginManager().callEvent(ev); if (!(revert = ev.isCancelled())) { //Yes, this is intended if (!to.equals(ev.getTo())) { //If plugins modify the destination this.teleport(ev.getTo(), null); } else { this.addMovement(this.x, this.y, this.z, this.yaw, this.pitch, this.yaw); } //Biome biome = Biome.biomes[level.getBiomeId(this.getFloorX(), this.getFloorZ())]; //sendTip(biome.getName() + " (" + biome.doesOverhang() + " " + biome.getBaseHeight() + "-" + biome.getHeightVariation() + ")"); } else { this.blocksAround = blocksAround; this.collisionBlocks = collidingBlocks; } } if (this.speed == null) speed = new Vector3(from.x - to.x, from.y - to.y, from.z - to.z); else this.speed.setComponents(from.x - to.x, from.y - to.y, from.z - to.z); } else { if (this.speed == null) speed = new Vector3(0, 0, 0); else this.speed.setComponents(0, 0, 0); } if (!revert && (this.isFoodEnabled() || this.getServer().getDifficulty() == 0)) { if ((this.isSurvival() || this.isAdventure())/* && !this.getRiddingOn() instanceof Entity*/) { //UpdateFoodExpLevel if (distance >= 0.05) { double jump = 0; double swimming = this.isInsideOfWater() ? 0.015 * distance : 0; if (swimming != 0) distance = 0; if (this.isSprinting()) { //Running if (this.inAirTicks == 3 && swimming == 0) { jump = 0.7; } this.getFoodData().updateFoodExpLevel(0.06 * distance + jump + swimming); } else { if (this.inAirTicks == 3 && swimming == 0) { jump = 0.2; } this.getFoodData().updateFoodExpLevel(0.01 * distance + jump + swimming); } } } } if (!revert && delta > 0.0001d) { Enchantment frostWalker = inventory.getBoots().getEnchantment(Enchantment.ID_FROST_WALKER); if (frostWalker != null && frostWalker.getLevel() > 0 && !this.isSpectator() && this.y >= 1 && this.y <= 255) { int radius = 2 + frostWalker.getLevel(); for (int coordX = this.getFloorX() - radius; coordX < this.getFloorX() + radius + 1; coordX++) { for (int coordZ = this.getFloorZ() - radius; coordZ < this.getFloorZ() + radius + 1; coordZ++) { Block block = level.getBlock(coordX, this.getFloorY() - 1, coordZ); int layer = 0; if ((block.getId() != Block.STILL_WATER && (block.getId() != Block.WATER || block.getDamage() != 0)) || block.up().getId() != Block.AIR) { block = block.getLevelBlockAtLayer(1); layer = 1; if ((block.getId() != Block.STILL_WATER && (block.getId() != Block.WATER || block.getDamage() != 0)) || block.up().getId() != Block.AIR) { continue; } } WaterFrostEvent ev = new WaterFrostEvent(block, this); server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { level.setBlock(block, layer, Block.get(Block.ICE_FROSTED), true, false); level.scheduleUpdate(level.getBlock(block, layer), ThreadLocalRandom.current().nextInt(20, 40)); } } } } } if (!revert) { int soulSpeedLevel = this.getInventory().getBoots().getEnchantmentLevel(Enchantment.ID_SOUL_SPEED); if (soulSpeedLevel > 0) { Block downBlock = this.getLevelBlock().down(); if (this.wasInSoulSandCompatible && !downBlock.isSoulSpeedCompatible()) { this.wasInSoulSandCompatible = false; this.soulSpeedMultiplier = 1; this.sendMovementSpeed(this.movementSpeed); } else if (!this.wasInSoulSandCompatible && downBlock.isSoulSpeedCompatible()) { this.wasInSoulSandCompatible = true; this.soulSpeedMultiplier = (soulSpeedLevel * 0.105f) + 1.3f; this.sendMovementSpeed(this.movementSpeed * this.soulSpeedMultiplier); } } } if (revert) { this.lastX = from.x; this.lastY = from.y; this.lastZ = from.z; this.lastYaw = from.yaw; this.lastPitch = from.pitch; // We have to send slightly above otherwise the player will fall into the ground. this.sendPosition(from.add(0, 0.00001, 0), from.yaw, from.pitch, MovePlayerPacket.MODE_RESET); //this.sendSettings(); this.forceMovement = new Vector3(from.x, from.y + 0.00001, from.z); } else { this.forceMovement = null; if (distanceSquared != 0 && this.nextChunkOrderRun > 20) { this.nextChunkOrderRun = 20; } } this.newPosition = null; } @Override public void addMovement(double x, double y, double z, double yaw, double pitch, double headYaw) { this.sendPosition(new Vector3(x, y, z), yaw, pitch, MovePlayerPacket.MODE_NORMAL, this.getViewers().values().toArray(EMPTY_ARRAY)); } @Override public boolean setMotion(Vector3 motion) { if (super.setMotion(motion)) { if (this.chunk != null) { this.addMotion(this.motionX, this.motionY, this.motionZ); //Send to others SetEntityMotionPacket pk = new SetEntityMotionPacket(); pk.eid = this.id; pk.motionX = (float) motion.x; pk.motionY = (float) motion.y; pk.motionZ = (float) motion.z; this.dataPacket(pk); //Send to self } if (this.motionY > 0) { //todo: check this this.startAirTicks = (int) ((-(Math.log(this.getGravity() / (this.getGravity() + this.getDrag() * this.motionY))) / this.getDrag()) * 2 + 5); } return true; } return false; } public void sendMovementSpeed(float movementSpeed) { Attribute attribute = Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(movementSpeed); this.setAttribute(attribute); } public void sendAttributes() { UpdateAttributesPacket pk = new UpdateAttributesPacket(); pk.entityId = this.getId(); pk.entries = new Attribute[]{ Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0), Attribute.getAttribute(Attribute.MAX_HUNGER).setValue(this.getFoodData().getLevel()), Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(this.getMovementSpeed()), Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(this.getExperienceLevel()), Attribute.getAttribute(Attribute.EXPERIENCE).setValue(((float) this.getExperience()) / calculateRequireExperience(this.getExperienceLevel())) }; this.dataPacket(pk); } @Override public boolean onUpdate(int currentTick) { if (!this.loggedIn) { return false; } int tickDiff = currentTick - this.lastUpdate; if (tickDiff <= 0) { return true; } this.messageCounter = 2; this.lastUpdate = currentTick; if (this.fishing != null && this.server.getTick() % 20 == 0) { if (this.distance(fishing) > 33) { this.stopFishing(false); } } if (!this.isAlive() && this.spawned) { ++this.deadTicks; if (this.deadTicks >= 10) { this.despawnFromAll(); } return true; } if (this.spawned) { this.processMovement(tickDiff); if (!this.isSpectator()) { this.checkNearEntities(); } this.entityBaseTick(tickDiff); if (this.getServer().getDifficulty() == 0 && this.level.getGameRules().getBoolean(GameRule.NATURAL_REGENERATION)) { if (this.getHealth() < this.getMaxHealth() && this.ticksLived % 20 == 0) { this.heal(1); } PlayerFood foodData = this.getFoodData(); if (foodData.getLevel() < 20 && this.ticksLived % 10 == 0) { foodData.addFoodLevel(1, 0); } } if (this.isOnFire() && this.lastUpdate % 10 == 0) { if (this.isCreative() && !this.isInsideOfFire()) { this.extinguish(); } else if (this.getLevel().isRaining()) { if (this.getLevel().canBlockSeeSky(this)) { this.extinguish(); } } } if (!this.isSpectator() && this.speed != null) { if (this.onGround) { if (this.inAirTicks != 0) { this.startAirTicks = 5; } this.inAirTicks = 0; this.highestPosition = this.y; } else { if (this.checkMovement && !this.isGliding() && !server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && this.inAirTicks > 20 && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION)) { double expectedVelocity = (-this.getGravity()) / ((double) this.getDrag()) - ((-this.getGravity()) / ((double) this.getDrag())) * Math.exp(-((double) this.getDrag()) * ((double) (this.inAirTicks - this.startAirTicks))); double diff = (this.speed.y - expectedVelocity) * (this.speed.y - expectedVelocity); Block block = level.getBlock(this); int blockId = block.getId(); boolean ignore = blockId == Block.LADDER || blockId == Block.VINES || blockId == Block.COBWEB || blockId == Block.SCAFFOLDING;// || (blockId == Block.SWEET_BERRY_BUSH && block.getDamage() > 0); if (!this.hasEffect(Effect.JUMP) && diff > 0.6 && expectedVelocity < this.speed.y && !ignore) { if (this.inAirTicks < 150) { this.setMotion(new Vector3(0, expectedVelocity, 0)); } else if (this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, "Flying is not enabled on this server")) { return false; } } if (ignore) { this.resetFallDistance(); } } if (this.y > highestPosition) { this.highestPosition = this.y; } if (this.isGliding()) this.resetFallDistance(); ++this.inAirTicks; } if (this.isSurvival() || this.isAdventure()) { if (this.getFoodData() != null) this.getFoodData().update(tickDiff); } } if (!this.isSleeping()) { this.timeSinceRest++; } } this.checkTeleportPosition(); if (currentTick % 10 == 0) { this.checkInteractNearby(); } if (this.spawned && this.dummyBossBars.size() > 0 && currentTick % 100 == 0) { this.dummyBossBars.values().forEach(DummyBossBar::updateBossEntityPosition); } updateBlockingFlag(); return true; } @Override public boolean entityBaseTick(int tickDiff) { boolean hasUpdated = false; if (isUsingItem()) { if (noShieldTicks < NO_SHIELD_DELAY) { noShieldTicks = NO_SHIELD_DELAY; hasUpdated = true; } } else { if (noShieldTicks > 0) { noShieldTicks -= tickDiff; hasUpdated = true; } if (noShieldTicks < 0) { noShieldTicks = 0; hasUpdated = true; } } return super.entityBaseTick(tickDiff) || hasUpdated; } public void checkInteractNearby() { int interactDistance = isCreative() ? 5 : 3; if (canInteract(this, interactDistance)) { if (getEntityPlayerLookingAt(interactDistance) != null) { EntityInteractable onInteract = getEntityPlayerLookingAt(interactDistance); setButtonText(onInteract.getInteractButtonText()); } else { setButtonText(""); } } else { setButtonText(""); } } /** * Returns the Entity the player is looking at currently * * @param maxDistance the maximum distance to check for entities * @return Entity|null either NULL if no entity is found or an instance of the entity */ public EntityInteractable getEntityPlayerLookingAt(int maxDistance) { timing.startTiming(); EntityInteractable entity = null; // just a fix because player MAY not be fully initialized if (temporalVector != null) { Entity[] nearbyEntities = level.getNearbyEntities(boundingBox.grow(maxDistance, maxDistance, maxDistance), this); // get all blocks in looking direction until the max interact distance is reached (it's possible that startblock isn't found!) try { BlockIterator itr = new BlockIterator(level, getPosition(), getDirectionVector(), getEyeHeight(), maxDistance); if (itr.hasNext()) { Block block; while (itr.hasNext()) { block = itr.next(); entity = getEntityAtPosition(nearbyEntities, block.getFloorX(), block.getFloorY(), block.getFloorZ()); if (entity != null) { break; } } } } catch (Exception ex) { // nothing to log here! } } timing.stopTiming(); return entity; } private EntityInteractable getEntityAtPosition(Entity[] nearbyEntities, int x, int y, int z) { for (Entity nearestEntity : nearbyEntities) { if (nearestEntity.getFloorX() == x && nearestEntity.getFloorY() == y && nearestEntity.getFloorZ() == z && nearestEntity instanceof EntityInteractable && ((EntityInteractable) nearestEntity).canDoInteraction()) { return (EntityInteractable) nearestEntity; } } return null; } public void checkNetwork() { if (!this.packetQueue.isEmpty()) { Player[] pArr = new Player[]{this}; List toBatch = new ArrayList<>(); DataPacket packet; while ((packet = this.packetQueue.poll()) != null) { toBatch.add(packet); } DataPacket[] arr = toBatch.toArray(new DataPacket[0]); this.server.batchPackets(pArr, arr, false); } if (!this.isOnline()) { return; } if (this.nextChunkOrderRun-- <= 0 || this.chunk == null) { this.orderChunks(); } if (!this.loadQueue.isEmpty() || !this.spawned) { this.sendNextChunk(); } } public boolean canInteract(Vector3 pos, double maxDistance) { return this.canInteract(pos, maxDistance, 6.0); } public boolean canInteract(Vector3 pos, double maxDistance, double maxDiff) { if (this.distanceSquared(pos) > maxDistance * maxDistance) { return false; } Vector2 dV = this.getDirectionPlane(); double dot = dV.dot(new Vector2(this.x, this.z)); double dot1 = dV.dot(new Vector2(pos.x, pos.z)); return (dot1 - dot) >= -maxDiff; } protected void processLogin() { if (!this.server.isWhitelisted((this.getName()).toLowerCase())) { this.kick(PlayerKickEvent.Reason.NOT_WHITELISTED, "Server is white-listed"); return; } else if (this.isBanned()) { String reason = this.server.getNameBans().getEntires().get(this.getName().toLowerCase()).getReason(); this.kick(PlayerKickEvent.Reason.NAME_BANNED, !reason.isEmpty() ? "You are banned. Reason: " + reason : "You are banned"); return; } else if (this.server.getIPBans().isBanned(this.getAddress())) { String reason = this.server.getIPBans().getEntires().get(this.getAddress()).getReason(); this.kick(PlayerKickEvent.Reason.IP_BANNED, !reason.isEmpty() ? "You are banned. Reason: " + reason : "You are banned"); return; } if (this.hasPermission(Server.BROADCAST_CHANNEL_USERS)) { this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_USERS, this); } if (this.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE)) { this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this); } Player oldPlayer = null; for (Player p : new ArrayList<>(this.server.getOnlinePlayers().values())) { if (p != this && p.getName() != null && p.getName().equalsIgnoreCase(this.getName()) || this.getUniqueId().equals(p.getUniqueId())) { oldPlayer = p; break; } } CompoundTag nbt; if (oldPlayer != null) { oldPlayer.saveNBT(); nbt = oldPlayer.namedTag; oldPlayer.close("", "disconnectionScreen.loggedinOtherLocation"); } else { File legacyDataFile = new File(server.getDataPath() + "players/" + this.username.toLowerCase() + ".dat"); File dataFile = new File(server.getDataPath() + "players/" + this.uuid.toString() + ".dat"); if (legacyDataFile.exists() && !dataFile.exists()) { nbt = this.server.getOfflinePlayerData(this.username, false); if (!legacyDataFile.delete()) { log.warn("Could not delete legacy player data for {}", this.username); } } else { nbt = this.server.getOfflinePlayerData(this.uuid, true); } } if (nbt == null) { this.close(this.getLeaveMessage(), "Invalid data"); return; } if (loginChainData.isXboxAuthed() && server.getPropertyBoolean("xbox-auth") || !server.getPropertyBoolean("xbox-auth")) { server.updateName(this.uuid, this.username); } this.playedBefore = (nbt.getLong("lastPlayed") - nbt.getLong("firstPlayed")) > 1; nbt.putString("NameTag", this.username); int exp = nbt.getInt("EXP"); int expLevel = nbt.getInt("expLevel"); this.setExperience(exp, expLevel); this.gamemode = nbt.getInt("playerGameType") & 0x03; if (this.server.getForceGamemode()) { this.gamemode = this.server.getGamemode(); nbt.putInt("playerGameType", this.gamemode); } this.adventureSettings = new AdventureSettings(this) .set(Type.WORLD_IMMUTABLE, isAdventure() || isSpectator()) .set(Type.WORLD_BUILDER, !isAdventure() && !isSpectator()) .set(Type.AUTO_JUMP, true) .set(Type.ALLOW_FLIGHT, isCreative()) .set(Type.NO_CLIP, isSpectator()); Level level; if ((level = this.server.getLevelByName(nbt.getString("Level"))) == null) { this.setLevel(this.server.getDefaultLevel()); nbt.putString("Level", this.level.getName()); Position spawnLocation = this.level.getSafeSpawn(); nbt.getList("Pos", DoubleTag.class) .add(new DoubleTag("0", spawnLocation.x)) .add(new DoubleTag("1", spawnLocation.y)) .add(new DoubleTag("2", spawnLocation.z)); } else { this.setLevel(level); } for (Tag achievement : nbt.getCompound("Achievements").getAllTags()) { if (!(achievement instanceof ByteTag)) { continue; } if (((ByteTag) achievement).getData() > 0) { this.achievements.add(achievement.getName()); } } nbt.putLong("lastPlayed", System.currentTimeMillis() / 1000); UUID uuid = getUniqueId(); nbt.putLong("UUIDLeast", uuid.getLeastSignificantBits()); nbt.putLong("UUIDMost", uuid.getMostSignificantBits()); if (this.server.getAutoSave()) { this.server.saveOfflinePlayerData(this.uuid, nbt, true); } this.sendPlayStatus(PlayStatusPacket.LOGIN_SUCCESS); this.server.onPlayerLogin(this); ListTag posList = nbt.getList("Pos", DoubleTag.class); super.init(this.level.getChunk((int) posList.get(0).data >> 4, (int) posList.get(2).data >> 4, true), nbt); if (!this.namedTag.contains("foodLevel")) { this.namedTag.putInt("foodLevel", 20); } int foodLevel = this.namedTag.getInt("foodLevel"); if (!this.namedTag.contains("FoodSaturationLevel")) { this.namedTag.putFloat("FoodSaturationLevel", 20); } float foodSaturationLevel = this.namedTag.getFloat("foodSaturationLevel"); this.foodData = new PlayerFood(this, foodLevel, foodSaturationLevel); if (this.isSpectator()) this.keepMovement = true; this.forceMovement = this.teleportPosition = this.getPosition(); if (!this.namedTag.contains("TimeSinceRest")) { this.namedTag.putInt("TimeSinceRest", 0); } this.timeSinceRest = this.namedTag.getInt("TimeSinceRest"); if (!this.server.isCheckMovement()) { this.checkMovement = false; } ResourcePacksInfoPacket infoPacket = new ResourcePacksInfoPacket(); infoPacket.resourcePackEntries = this.server.getResourcePackManager().getResourceStack(); infoPacket.mustAccept = this.server.getForceResources(); this.dataPacket(infoPacket); } protected void completeLoginSequence() { PlayerLoginEvent ev; this.server.getPluginManager().callEvent(ev = new PlayerLoginEvent(this, "Plugin reason")); if (ev.isCancelled()) { this.close(this.getLeaveMessage(), ev.getKickMessage()); return; } Level level = this.server.getLevelByName(this.namedTag.getString("SpawnLevel")); if(level != null){ this.spawnPosition = new Position(this.namedTag.getInt("SpawnX"), this.namedTag.getInt("SpawnY"), this.namedTag.getInt("SpawnZ"), level); }else{ this.spawnPosition = this.level.getSafeSpawn(); } spawnPosition = this.getSpawn(); StartGamePacket startGamePacket = new StartGamePacket(); startGamePacket.entityUniqueId = this.id; startGamePacket.entityRuntimeId = this.id; startGamePacket.playerGamemode = getClientFriendlyGamemode(this.gamemode); startGamePacket.x = (float) this.x; startGamePacket.y = (float) this.y; startGamePacket.z = (float) this.z; startGamePacket.yaw = (float) this.yaw; startGamePacket.pitch = (float) this.pitch; startGamePacket.seed = -1; startGamePacket.dimension = /*(byte) (this.level.getDimension() & 0xff)*/0; startGamePacket.worldGamemode = getClientFriendlyGamemode(this.gamemode); startGamePacket.difficulty = this.server.getDifficulty(); startGamePacket.spawnX = spawnPosition.getFloorX(); startGamePacket.spawnY = spawnPosition.getFloorY(); startGamePacket.spawnZ = spawnPosition.getFloorZ(); startGamePacket.hasAchievementsDisabled = true; startGamePacket.dayCycleStopTime = -1; startGamePacket.rainLevel = 0; startGamePacket.lightningLevel = 0; startGamePacket.commandsEnabled = this.isEnableClientCommand(); startGamePacket.gameRules = getLevel().getGameRules(); startGamePacket.levelId = ""; startGamePacket.worldName = this.getServer().getNetwork().getName(); startGamePacket.generator = 1; //0 old, 1 infinite, 2 flat startGamePacket.dimension = (byte) getLevel().getDimension(); //startGamePacket.isInventoryServerAuthoritative = true; this.dataPacket(startGamePacket); this.dataPacket(new BiomeDefinitionListPacket()); this.dataPacket(new AvailableEntityIdentifiersPacket()); this.inventory.sendCreativeContents(); this.getAdventureSettings().update(); this.sendAttributes(); this.sendPotionEffects(this); this.sendData(this); this.loggedIn = true; this.level.sendTime(this); this.sendAttributes(); this.setNameTagVisible(true); this.setNameTagAlwaysVisible(true); this.setCanClimb(true); this.server.getLogger().info(this.getServer().getLanguage().translateString("nukkit.player.logIn", TextFormat.AQUA + this.username + TextFormat.WHITE, this.getAddress(), String.valueOf(this.getPort()), String.valueOf(this.id), this.level.getName(), String.valueOf(NukkitMath.round(this.x, 4)), String.valueOf(NukkitMath.round(this.y, 4)), String.valueOf(NukkitMath.round(this.z, 4)))); if (this.isOp() || this.hasPermission("nukkit.textcolor")) { this.setRemoveFormat(false); } this.server.addOnlinePlayer(this); this.server.onPlayerCompleteLoginSequence(this); } public void handleDataPacket(DataPacket packet) { if (!connected) { return; } try (Timing timing = Timings.getReceiveDataPacketTiming(packet)) { DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return; } if (packet.pid() == ProtocolInfo.BATCH_PACKET) { this.server.getNetwork().processBatch((BatchPacket) packet, this); return; } if (log.isTraceEnabled() && !server.isIgnoredPacket(packet.getClass())) { log.trace("Inbound {}: {}", this.getName(), packet); } packetswitch: switch (packet.pid()) { case ProtocolInfo.LOGIN_PACKET: if (this.loggedIn) { break; } LoginPacket loginPacket = (LoginPacket) packet; String message; if (!ProtocolInfo.SUPPORTED_PROTOCOLS.contains(loginPacket.getProtocol())) { if (loginPacket.getProtocol() < ProtocolInfo.CURRENT_PROTOCOL) { message = "disconnectionScreen.outdatedClient"; this.sendPlayStatus(PlayStatusPacket.LOGIN_FAILED_CLIENT, true); } else { message = "disconnectionScreen.outdatedServer"; this.sendPlayStatus(PlayStatusPacket.LOGIN_FAILED_SERVER, true); } if (((LoginPacket) packet).protocol < 137) { DisconnectPacket disconnectPacket = new DisconnectPacket(); disconnectPacket.message = message; disconnectPacket.encode(); BatchPacket batch = new BatchPacket(); batch.payload = disconnectPacket.getBuffer(); this.directDataPacket(batch); // Still want to run close() to allow the player to be removed properly } this.close("", message, false); break; } this.username = TextFormat.clean(loginPacket.username); this.displayName = this.username; this.iusername = this.username.toLowerCase(); this.setDataProperty(new StringEntityData(DATA_NAMETAG, this.username), false); this.loginChainData = ClientChainData.read(loginPacket); if (!loginChainData.isXboxAuthed() && server.getPropertyBoolean("xbox-auth")) { this.close("", "disconnectionScreen.notAuthenticated"); break; } if (this.server.getOnlinePlayers().size() >= this.server.getMaxPlayers() && this.kick(PlayerKickEvent.Reason.SERVER_FULL, "disconnectionScreen.serverFull", false)) { break; } this.randomClientId = loginPacket.clientId; this.uuid = loginPacket.clientUUID; this.rawUUID = Binary.writeUUID(this.uuid); boolean valid = true; int len = loginPacket.username.length(); if (len > 16 || len < 3) { valid = false; } for (int i = 0; i < len && valid; i++) { char c = loginPacket.username.charAt(i); if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == ' ' ) { continue; } valid = false; break; } if (!valid || Objects.equals(this.iusername, "rcon") || Objects.equals(this.iusername, "console")) { this.close("", "disconnectionScreen.invalidName"); break; } if (!loginPacket.skin.isValid()) { this.close("", "disconnectionScreen.invalidSkin"); break; } else { Skin skin = loginPacket.skin; if (this.server.isForceSkinTrusted()) { skin.setTrusted(true); } this.setSkin(skin); } PlayerPreLoginEvent playerPreLoginEvent; this.server.getPluginManager().callEvent(playerPreLoginEvent = new PlayerPreLoginEvent(this, "Plugin reason")); if (playerPreLoginEvent.isCancelled()) { this.close("", playerPreLoginEvent.getKickMessage()); break; } Player playerInstance = this; this.preLoginEventTask = new AsyncTask() { private PlayerAsyncPreLoginEvent e; @Override public void onRun() { e = new PlayerAsyncPreLoginEvent(username, uuid, loginChainData.getXUID(), Player.this.getAddress(), Player.this.getPort()); server.getPluginManager().callEvent(e); } @Override public void onCompletion(Server server) { if (!playerInstance.closed) { if (e.getLoginResult() == LoginResult.KICK) { playerInstance.close(e.getKickMessage(), e.getKickMessage()); } else if (playerInstance.shouldLogin) { playerInstance.completeLoginSequence(); for (Consumer action : e.getScheduledActions()) { action.accept(server); } } } } }; this.server.getScheduler().scheduleAsyncTask(this.preLoginEventTask); this.processLogin(); break; case ProtocolInfo.RESOURCE_PACK_CLIENT_RESPONSE_PACKET: ResourcePackClientResponsePacket responsePacket = (ResourcePackClientResponsePacket) packet; switch (responsePacket.responseStatus) { case ResourcePackClientResponsePacket.STATUS_REFUSED: this.close("", "disconnectionScreen.noReason"); break; case ResourcePackClientResponsePacket.STATUS_SEND_PACKS: for (ResourcePackClientResponsePacket.Entry entry : responsePacket.packEntries) { ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(entry.uuid); if (resourcePack == null) { this.close("", "disconnectionScreen.resourcePack"); break; } ResourcePackDataInfoPacket dataInfoPacket = new ResourcePackDataInfoPacket(); dataInfoPacket.packId = resourcePack.getPackId(); dataInfoPacket.maxChunkSize = 1048576; //megabyte dataInfoPacket.chunkCount = resourcePack.getPackSize() / dataInfoPacket.maxChunkSize; dataInfoPacket.compressedPackSize = resourcePack.getPackSize(); dataInfoPacket.sha256 = resourcePack.getSha256(); this.dataPacket(dataInfoPacket); } break; case ResourcePackClientResponsePacket.STATUS_HAVE_ALL_PACKS: ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); stackPacket.mustAccept = this.server.getForceResources(); stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack(); this.dataPacket(stackPacket); break; case ResourcePackClientResponsePacket.STATUS_COMPLETED: this.shouldLogin = true; if (this.preLoginEventTask.isFinished()) { this.preLoginEventTask.onCompletion(server); } break; } break; case ProtocolInfo.RESOURCE_PACK_CHUNK_REQUEST_PACKET: ResourcePackChunkRequestPacket requestPacket = (ResourcePackChunkRequestPacket) packet; ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(requestPacket.packId); if (resourcePack == null) { this.close("", "disconnectionScreen.resourcePack"); break; } ResourcePackChunkDataPacket dataPacket = new ResourcePackChunkDataPacket(); dataPacket.packId = resourcePack.getPackId(); dataPacket.chunkIndex = requestPacket.chunkIndex; dataPacket.data = resourcePack.getPackChunk(1048576 * requestPacket.chunkIndex, 1048576); dataPacket.progress = 1048576 * requestPacket.chunkIndex; this.dataPacket(dataPacket); break; case ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET: if (this.locallyInitialized) { break; } this.locallyInitialized = true; PlayerLocallyInitializedEvent locallyInitializedEvent = new PlayerLocallyInitializedEvent(this); this.server.getPluginManager().callEvent(locallyInitializedEvent); break; case ProtocolInfo.PLAYER_SKIN_PACKET: PlayerSkinPacket skinPacket = (PlayerSkinPacket) packet; Skin skin = skinPacket.skin; if (!skin.isValid()) { break; } if (this.server.isForceSkinTrusted()) { skin.setTrusted(true); } PlayerChangeSkinEvent playerChangeSkinEvent = new PlayerChangeSkinEvent(this, skin); playerChangeSkinEvent.setCancelled(TimeUnit.SECONDS.toMillis(this.server.getPlayerSkinChangeCooldown()) > System.currentTimeMillis() - this.lastSkinChange); this.server.getPluginManager().callEvent(playerChangeSkinEvent); if (!playerChangeSkinEvent.isCancelled()) { this.lastSkinChange = System.currentTimeMillis(); this.setSkin(skin); } break; case ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET: Optional packetName = Arrays.stream(ProtocolInfo.class.getDeclaredFields()) .filter(field -> field.getType() == Byte.TYPE) .filter(field -> { try { return field.getByte(null) == ((PacketViolationWarningPacket) packet).packetId; } catch (IllegalAccessException e) { return false; } }).map(Field::getName).findFirst(); log.warn("Received packet violation warning"+ packetName.map(name-> " for packet "+name).orElse("")+": " + packet.toString()); break; case ProtocolInfo.EMOTE_PACKET: for (Player viewer : this.getViewers().values()) { viewer.dataPacket(packet); } return; case ProtocolInfo.PLAYER_INPUT_PACKET: if (!this.isAlive() || !this.spawned) { break; } PlayerInputPacket ipk = (PlayerInputPacket) packet; if (riding instanceof EntityMinecartAbstract) { ((EntityMinecartAbstract) riding).setCurrentSpeed(ipk.motionY); } break; case ProtocolInfo.MOVE_PLAYER_PACKET: if (this.teleportPosition != null) { break; } MovePlayerPacket movePlayerPacket = (MovePlayerPacket) packet; Vector3 newPos = new Vector3(movePlayerPacket.x, movePlayerPacket.y - this.getEyeHeight(), movePlayerPacket.z); if (newPos.distanceSquared(this) < 0.01 && movePlayerPacket.yaw % 360 == this.yaw && movePlayerPacket.pitch % 360 == this.pitch) { break; } if (newPos.distanceSquared(this) > 100) { this.sendPosition(this, movePlayerPacket.yaw, movePlayerPacket.pitch, MovePlayerPacket.MODE_RESET); break; } boolean revert = false; if (!this.isAlive() || !this.spawned) { revert = true; this.forceMovement = new Vector3(this.x, this.y, this.z); } if (this.forceMovement != null && (newPos.distanceSquared(this.forceMovement) > 0.1 || revert)) { this.sendPosition(this.forceMovement, movePlayerPacket.yaw, movePlayerPacket.pitch, MovePlayerPacket.MODE_RESET); } else { movePlayerPacket.yaw %= 360; movePlayerPacket.pitch %= 360; if (movePlayerPacket.yaw < 0) { movePlayerPacket.yaw += 360; } this.setRotation(movePlayerPacket.yaw, movePlayerPacket.pitch); this.newPosition = newPos; this.positionChanged = true; this.forceMovement = null; } if (riding != null) { if (riding instanceof EntityBoat) { riding.setPositionAndRotation(this.temporalVector.setComponents(movePlayerPacket.x, movePlayerPacket.y - 1, movePlayerPacket.z), (movePlayerPacket.headYaw + 90) % 360, 0); } } break; case ProtocolInfo.ADVENTURE_SETTINGS_PACKET: //TODO: player abilities, check for other changes AdventureSettingsPacket adventureSettingsPacket = (AdventureSettingsPacket) packet; if (!server.getAllowFlight() && adventureSettingsPacket.getFlag(AdventureSettingsPacket.FLYING) && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT)) { this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, "Flying is not enabled on this server"); break; } PlayerToggleFlightEvent playerToggleFlightEvent = new PlayerToggleFlightEvent(this, adventureSettingsPacket.getFlag(AdventureSettingsPacket.FLYING)); this.server.getPluginManager().callEvent(playerToggleFlightEvent); if (playerToggleFlightEvent.isCancelled()) { this.getAdventureSettings().update(); } else { this.getAdventureSettings().set(Type.FLYING, playerToggleFlightEvent.isFlying()); } break; case ProtocolInfo.MOB_EQUIPMENT_PACKET: if (!this.spawned || !this.isAlive()) { break; } MobEquipmentPacket mobEquipmentPacket = (MobEquipmentPacket) packet; Inventory inv = this.getWindowById(mobEquipmentPacket.windowId); if (inv == null) { this.server.getLogger().debug("Player " + this.getName() + " has no open container with window ID " + mobEquipmentPacket.windowId); return; } Item item = inv.getItem(mobEquipmentPacket.hotbarSlot); if (!item.equals(mobEquipmentPacket.item)) { this.server.getLogger().debug("Tried to equip " + mobEquipmentPacket.item + " but have " + item + " in target slot"); inv.sendContents(this); return; } if (inv instanceof PlayerInventory) { ((PlayerInventory) inv).equipItem(mobEquipmentPacket.hotbarSlot); } this.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_ACTION, false); break; case ProtocolInfo.PLAYER_ACTION_PACKET: PlayerActionPacket playerActionPacket = (PlayerActionPacket) packet; if (!this.spawned || (!this.isAlive() && playerActionPacket.action != PlayerActionPacket.ACTION_RESPAWN && playerActionPacket.action != PlayerActionPacket.ACTION_DIMENSION_CHANGE_REQUEST)) { break; } playerActionPacket.entityId = this.id; Vector3 pos = new Vector3(playerActionPacket.x, playerActionPacket.y, playerActionPacket.z); BlockFace face = BlockFace.fromIndex(playerActionPacket.face); actionswitch: switch (playerActionPacket.action) { case PlayerActionPacket.ACTION_START_BREAK: long currentBreak = System.currentTimeMillis(); BlockVector3 currentBreakPosition = new BlockVector3(playerActionPacket.x, playerActionPacket.y, playerActionPacket.z); // HACK: Client spams multiple left clicks so we need to skip them. if ((lastBreakPosition.equals(currentBreakPosition) && (currentBreak - this.lastBreak) < 10) || pos.distanceSquared(this) > 100) { break; } Block target = this.level.getBlock(pos); PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(this, this.inventory.getItemInHand(), target, face, target.getId() == 0 ? Action.LEFT_CLICK_AIR : Action.LEFT_CLICK_BLOCK); this.getServer().getPluginManager().callEvent(playerInteractEvent); if (playerInteractEvent.isCancelled()) { this.inventory.sendHeldItem(this); break; } switch (target.getId()) { case Block.NOTEBLOCK: ((BlockNoteblock) target).emitSound(); break actionswitch; case Block.DRAGON_EGG: ((BlockDragonEgg) target).teleport(); break actionswitch; case Block.LECTERN: ((BlockLectern) target).dropBook(this); break; } Block block = target.getSide(face); if (block.getId() == Block.FIRE || block.getId() == BlockID.SOUL_FIRE) { this.level.setBlock(block, Block.get(BlockID.AIR), true); this.level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_EXTINGUISH_FIRE); break; } if (block.getId() == Block.SWEET_BERRY_BUSH && block.getDamage() == 0) { Item oldItem = playerInteractEvent.getItem(); Item i = this.level.useBreakOn(block, oldItem, this, true); if (this.isSurvival()) { this.getFoodData().updateFoodExpLevel(0.025); if (!i.equals(oldItem) || i.getCount() != oldItem.getCount()) { inventory.setItemInHand(i); inventory.sendHeldItem(this.getViewers().values()); } } break; } if (!block.isBlockChangeAllowed(this)) { break; } if (!this.isCreative()) { //improved this to take stuff like swimming, ladders, enchanted tools into account, fix wrong tool break time calculations for bad tools (pmmp/PocketMine-MP#211) //Done by lmlstarqaq double breakTime = Math.ceil(target.calculateBreakTime(this.inventory.getItemInHand(), this) * 20); if (breakTime > 0) { LevelEventPacket pk = new LevelEventPacket(); pk.evid = LevelEventPacket.EVENT_BLOCK_START_BREAK; pk.x = (float) pos.x; pk.y = (float) pos.y; pk.z = (float) pos.z; pk.data = (int) (65535 / breakTime); this.getLevel().addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); } } this.breakingBlock = target; this.lastBreak = currentBreak; this.lastBreakPosition = currentBreakPosition; break; case PlayerActionPacket.ACTION_ABORT_BREAK: case PlayerActionPacket.ACTION_STOP_BREAK: LevelEventPacket pk = new LevelEventPacket(); pk.evid = LevelEventPacket.EVENT_BLOCK_STOP_BREAK; pk.x = (float) pos.x; pk.y = (float) pos.y; pk.z = (float) pos.z; pk.data = 0; this.getLevel().addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); this.breakingBlock = null; break; case PlayerActionPacket.ACTION_GET_UPDATED_BLOCK: break; //TODO case PlayerActionPacket.ACTION_DROP_ITEM: break; //TODO case PlayerActionPacket.ACTION_STOP_SLEEPING: this.stopSleep(); break; case PlayerActionPacket.ACTION_RESPAWN: if (!this.spawned || this.isAlive() || !this.isOnline()) { break; } this.respawn(); break; case PlayerActionPacket.ACTION_JUMP: PlayerJumpEvent playerJumpEvent = new PlayerJumpEvent(this); this.server.getPluginManager().callEvent(playerJumpEvent); break packetswitch; case PlayerActionPacket.ACTION_START_SPRINT: PlayerToggleSprintEvent playerToggleSprintEvent = new PlayerToggleSprintEvent(this, true); this.server.getPluginManager().callEvent(playerToggleSprintEvent); if (playerToggleSprintEvent.isCancelled()) { this.sendData(this); } else { this.setSprinting(true); } break packetswitch; case PlayerActionPacket.ACTION_STOP_SPRINT: playerToggleSprintEvent = new PlayerToggleSprintEvent(this, false); this.server.getPluginManager().callEvent(playerToggleSprintEvent); if (playerToggleSprintEvent.isCancelled()) { this.sendData(this); } else { this.setSprinting(false); } break packetswitch; case PlayerActionPacket.ACTION_START_SNEAK: PlayerToggleSneakEvent playerToggleSneakEvent = new PlayerToggleSneakEvent(this, true); this.server.getPluginManager().callEvent(playerToggleSneakEvent); if (playerToggleSneakEvent.isCancelled()) { this.sendData(this); } else { this.setSneaking(true); } break packetswitch; case PlayerActionPacket.ACTION_STOP_SNEAK: playerToggleSneakEvent = new PlayerToggleSneakEvent(this, false); this.server.getPluginManager().callEvent(playerToggleSneakEvent); if (playerToggleSneakEvent.isCancelled()) { this.sendData(this); } else { this.setSneaking(false); } break packetswitch; case PlayerActionPacket.ACTION_DIMENSION_CHANGE_ACK: this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_NORMAL); break; //TODO case PlayerActionPacket.ACTION_START_GLIDE: PlayerToggleGlideEvent playerToggleGlideEvent = new PlayerToggleGlideEvent(this, true); this.server.getPluginManager().callEvent(playerToggleGlideEvent); if (playerToggleGlideEvent.isCancelled()) { this.sendData(this); } else { this.setGliding(true); } break packetswitch; case PlayerActionPacket.ACTION_STOP_GLIDE: playerToggleGlideEvent = new PlayerToggleGlideEvent(this, false); this.server.getPluginManager().callEvent(playerToggleGlideEvent); if (playerToggleGlideEvent.isCancelled()) { this.sendData(this); } else { this.setGliding(false); } break packetswitch; case PlayerActionPacket.ACTION_CONTINUE_BREAK: if (this.isBreakingBlock()) { block = this.level.getBlock(pos); this.level.addParticle(new PunchBlockParticle(pos, block, face)); } break; case PlayerActionPacket.ACTION_START_SWIMMING: PlayerToggleSwimEvent ptse = new PlayerToggleSwimEvent(this, true); this.server.getPluginManager().callEvent(ptse); if (ptse.isCancelled()) { this.sendData(this); } else { this.setSwimming(true); } break; case PlayerActionPacket.ACTION_STOP_SWIMMING: ptse = new PlayerToggleSwimEvent(this, false); this.server.getPluginManager().callEvent(ptse); if (ptse.isCancelled()) { this.sendData(this); } else { this.setSwimming(false); } break; } this.setUsingItem(false); break; case ProtocolInfo.MOB_ARMOR_EQUIPMENT_PACKET: break; case ProtocolInfo.MODAL_FORM_RESPONSE_PACKET: if (!this.spawned || !this.isAlive()) { break; } ModalFormResponsePacket modalFormPacket = (ModalFormResponsePacket) packet; if (formWindows.containsKey(modalFormPacket.formId)) { FormWindow window = formWindows.remove(modalFormPacket.formId); window.setResponse(modalFormPacket.data.trim()); PlayerFormRespondedEvent event = new PlayerFormRespondedEvent(this, modalFormPacket.formId, window); getServer().getPluginManager().callEvent(event); } else if (serverSettings.containsKey(modalFormPacket.formId)) { FormWindow window = serverSettings.get(modalFormPacket.formId); window.setResponse(modalFormPacket.data.trim()); PlayerSettingsRespondedEvent event = new PlayerSettingsRespondedEvent(this, modalFormPacket.formId, window); getServer().getPluginManager().callEvent(event); //Set back new settings if not been cancelled if (!event.isCancelled() && window instanceof FormWindowCustom) ((FormWindowCustom) window).setElementsFromResponse(); } break; case ProtocolInfo.INTERACT_PACKET: if (!this.spawned || !this.isAlive()) { break; } InteractPacket interactPacket = (InteractPacket) packet; if (interactPacket.action != InteractPacket.ACTION_MOUSEOVER || interactPacket.target != 0) { this.craftingType = CRAFTING_SMALL; //this.resetCraftingGridType(); } Entity targetEntity = this.level.getEntity(interactPacket.target); if (targetEntity == null || !this.isAlive() || !targetEntity.isAlive()) { break; } if (targetEntity instanceof EntityItem || targetEntity instanceof EntityArrow || targetEntity instanceof EntityXPOrb) { this.kick(PlayerKickEvent.Reason.INVALID_PVE, "Attempting to interact with an invalid entity"); this.server.getLogger().warning(this.getServer().getLanguage().translateString("nukkit.player.invalidEntity", this.getName())); break; } item = this.inventory.getItemInHand(); switch (interactPacket.action) { case InteractPacket.ACTION_MOUSEOVER: if (interactPacket.target == 0) { break packetswitch; } this.getServer().getPluginManager().callEvent(new PlayerMouseOverEntityEvent(this, targetEntity)); break; case InteractPacket.ACTION_VEHICLE_EXIT: if (!(targetEntity instanceof EntityRideable) || this.riding == null) { break; } ((EntityRideable) riding).mountEntity(this); break; case InteractPacket.ACTION_OPEN_INVENTORY: if (targetEntity.getId() != this.getId()) break; if (!this.inventoryOpen) { this.inventory.open(this); this.inventoryOpen = true; } break; } break; case ProtocolInfo.BLOCK_PICK_REQUEST_PACKET: BlockPickRequestPacket pickRequestPacket = (BlockPickRequestPacket) packet; Block block = this.level.getBlock(this.temporalVector.setComponents(pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z)); item = block.toItem(); if (pickRequestPacket.addUserData) { BlockEntity blockEntity = this.getLevel().getBlockEntity(new Vector3(pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z)); if (blockEntity != null) { CompoundTag nbt = blockEntity.getCleanedNBT(); if (nbt != null) { item.setCustomBlockData(nbt); item.setLore("+(DATA)"); } } } PlayerBlockPickEvent pickEvent = new PlayerBlockPickEvent(this, block, item); if (this.isSpectator()) { log.debug("Got block-pick request from " + this.getName() + " when in spectator mode"); pickEvent.setCancelled(); } this.server.getPluginManager().callEvent(pickEvent); if (!pickEvent.isCancelled()) { boolean itemExists = false; int itemSlot = -1; for (int slot = 0; slot < this.inventory.getSize(); slot++) { if (this.inventory.getItem(slot).equals(pickEvent.getItem())) { if (slot < this.inventory.getHotbarSize()) { this.inventory.setHeldItemSlot(slot); } else { itemSlot = slot; } itemExists = true; break; } } for (int slot = 0; slot < this.inventory.getHotbarSize(); slot++) { if (this.inventory.getItem(slot).isNull()) { if (!itemExists && this.isCreative()) { this.inventory.setHeldItemSlot(slot); this.inventory.setItemInHand(pickEvent.getItem()); break packetswitch; } else if (itemSlot > -1) { this.inventory.setHeldItemSlot(slot); this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); this.inventory.clear(itemSlot, true); break packetswitch; } } } if (!itemExists && this.isCreative()) { Item itemInHand = this.inventory.getItemInHand(); this.inventory.setItemInHand(pickEvent.getItem()); if (!this.inventory.isFull()) { for (int slot = 0; slot < this.inventory.getSize(); slot++) { if (this.inventory.getItem(slot).isNull()) { this.inventory.setItem(slot, itemInHand); break; } } } } else if (itemSlot > -1) { Item itemInHand = this.inventory.getItemInHand(); this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); this.inventory.setItem(itemSlot, itemInHand); } } break; case ProtocolInfo.ANIMATE_PACKET: if (!this.spawned || !this.isAlive()) { break; } PlayerAnimationEvent animationEvent = new PlayerAnimationEvent(this, ((AnimatePacket) packet).action); this.server.getPluginManager().callEvent(animationEvent); if (animationEvent.isCancelled()) { break; } AnimatePacket.Action animation = animationEvent.getAnimationType(); switch (animation) { case ROW_RIGHT: case ROW_LEFT: if (this.riding instanceof EntityBoat) { ((EntityBoat) this.riding).onPaddle(animation, ((AnimatePacket) packet).rowingTime); } break; } if (animationEvent.getAnimationType() == AnimatePacket.Action.SWING_ARM) { setNoShieldTicks(NO_SHIELD_DELAY); } AnimatePacket animatePacket = new AnimatePacket(); animatePacket.eid = this.getId(); animatePacket.action = animationEvent.getAnimationType(); Server.broadcastPacket(this.getViewers().values(), animatePacket); break; case ProtocolInfo.SET_HEALTH_PACKET: //use UpdateAttributePacket instead break; case ProtocolInfo.ENTITY_EVENT_PACKET: if (!this.spawned || !this.isAlive()) { break; } EntityEventPacket entityEventPacket = (EntityEventPacket) packet; if (craftingType != CRAFTING_ANVIL && entityEventPacket.event != EntityEventPacket.ENCHANT) { this.craftingType = CRAFTING_SMALL; //this.resetCraftingGridType(); } if (entityEventPacket.event == EntityEventPacket.EATING_ITEM) { if (entityEventPacket.data == 0 || entityEventPacket.eid != this.id) { break; } entityEventPacket.eid = this.id; entityEventPacket.isEncoded = false; this.dataPacket(entityEventPacket); Server.broadcastPacket(this.getViewers().values(), entityEventPacket); } break; case ProtocolInfo.COMMAND_REQUEST_PACKET: if (!this.spawned || !this.isAlive()) { break; } this.craftingType = CRAFTING_SMALL; CommandRequestPacket commandRequestPacket = (CommandRequestPacket) packet; PlayerCommandPreprocessEvent playerCommandPreprocessEvent = new PlayerCommandPreprocessEvent(this, commandRequestPacket.command); this.server.getPluginManager().callEvent(playerCommandPreprocessEvent); if (playerCommandPreprocessEvent.isCancelled()) { break; } Timings.playerCommandTimer.startTiming(); this.server.dispatchCommand(playerCommandPreprocessEvent.getPlayer(), playerCommandPreprocessEvent.getMessage().substring(1)); Timings.playerCommandTimer.stopTiming(); break; case ProtocolInfo.TEXT_PACKET: if (!this.spawned || !this.isAlive()) { break; } TextPacket textPacket = (TextPacket) packet; if (textPacket.type == TextPacket.TYPE_CHAT) { String chatMessage = textPacket.message; int breakLine = chatMessage.indexOf('\n'); // Chat messages shouldn't contain break lines so ignore text afterwards if (breakLine != -1) { chatMessage = chatMessage.substring(0, breakLine); } this.chat(chatMessage); } break; case ProtocolInfo.CONTAINER_CLOSE_PACKET: ContainerClosePacket containerClosePacket = (ContainerClosePacket) packet; if (!this.spawned || containerClosePacket.windowId == ContainerIds.INVENTORY && !inventoryOpen) { break; } if (this.windowIndex.containsKey(containerClosePacket.windowId)) { this.server.getPluginManager().callEvent(new InventoryCloseEvent(this.windowIndex.get(containerClosePacket.windowId), this)); if (containerClosePacket.windowId == ContainerIds.INVENTORY) this.inventoryOpen = false; this.removeWindow(this.windowIndex.get(containerClosePacket.windowId)); } if (containerClosePacket.windowId == -1) { this.craftingType = CRAFTING_SMALL; this.resetCraftingGridType(); this.addWindow(this.craftingGrid, ContainerIds.NONE); ContainerClosePacket pk = new ContainerClosePacket(); pk.wasServerInitiated = false; pk.windowId = -1; this.dataPacket(pk); } break; case ProtocolInfo.CRAFTING_EVENT_PACKET: CraftingEventPacket craftingEventPacket = (CraftingEventPacket) packet; if (craftingType == CRAFTING_BIG && craftingEventPacket.type == CraftingEventPacket.TYPE_WORKBENCH || craftingType == CRAFTING_SMALL && craftingEventPacket.type == CraftingEventPacket.TYPE_INVENTORY) { if (craftingTransaction != null) { craftingTransaction.setReadyToExecute(true); if (craftingTransaction.getPrimaryOutput() == null) { craftingTransaction.setPrimaryOutput(craftingEventPacket.output[0]); } } } break; case ProtocolInfo.BLOCK_ENTITY_DATA_PACKET: if (!this.spawned || !this.isAlive()) { break; } BlockEntityDataPacket blockEntityDataPacket = (BlockEntityDataPacket) packet; this.craftingType = CRAFTING_SMALL; this.resetCraftingGridType(); pos = new Vector3(blockEntityDataPacket.x, blockEntityDataPacket.y, blockEntityDataPacket.z); if (pos.distanceSquared(this) > 10000) { break; } BlockEntity t = this.level.getBlockEntity(pos); if (t instanceof BlockEntitySpawnable) { CompoundTag nbt; try { nbt = NBTIO.read(blockEntityDataPacket.namedTag, ByteOrder.LITTLE_ENDIAN, true); } catch (IOException e) { throw new RuntimeException(e); } if (!((BlockEntitySpawnable) t).updateCompoundTag(nbt, this)) { ((BlockEntitySpawnable) t).spawnTo(this); } } break; case ProtocolInfo.REQUEST_CHUNK_RADIUS_PACKET: RequestChunkRadiusPacket requestChunkRadiusPacket = (RequestChunkRadiusPacket) packet; ChunkRadiusUpdatedPacket chunkRadiusUpdatePacket = new ChunkRadiusUpdatedPacket(); this.chunkRadius = Math.max(3, Math.min(requestChunkRadiusPacket.radius, this.viewDistance)); chunkRadiusUpdatePacket.radius = this.chunkRadius; this.dataPacket(chunkRadiusUpdatePacket); break; case ProtocolInfo.SET_PLAYER_GAME_TYPE_PACKET: SetPlayerGameTypePacket setPlayerGameTypePacket = (SetPlayerGameTypePacket) packet; if (setPlayerGameTypePacket.gamemode != this.gamemode) { if (!this.hasPermission("nukkit.command.gamemode")) { SetPlayerGameTypePacket setPlayerGameTypePacket1 = new SetPlayerGameTypePacket(); setPlayerGameTypePacket1.gamemode = this.gamemode & 0x01; this.dataPacket(setPlayerGameTypePacket1); this.getAdventureSettings().update(); break; } this.setGamemode(setPlayerGameTypePacket.gamemode, true); Command.broadcastCommandMessage(this, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(this.gamemode))); } break; case ProtocolInfo.ITEM_FRAME_DROP_ITEM_PACKET: ItemFrameDropItemPacket itemFrameDropItemPacket = (ItemFrameDropItemPacket) packet; Vector3 vector3 = this.temporalVector.setComponents(itemFrameDropItemPacket.x, itemFrameDropItemPacket.y, itemFrameDropItemPacket.z); BlockEntity blockEntityItemFrame = this.level.getBlockEntity(vector3); BlockEntityItemFrame itemFrame = (BlockEntityItemFrame) blockEntityItemFrame; if (itemFrame != null) { block = itemFrame.getBlock(); Item itemDrop = itemFrame.getItem(); ItemFrameDropItemEvent itemFrameDropItemEvent = new ItemFrameDropItemEvent(this, block, itemFrame, itemDrop); this.server.getPluginManager().callEvent(itemFrameDropItemEvent); if (!itemFrameDropItemEvent.isCancelled()) { if (itemDrop.getId() != Item.AIR) { vector3 = this.temporalVector.setComponents(itemFrame.x + 0.5, itemFrame.y, itemFrame.z + 0.5); this.level.dropItem(vector3, itemDrop); itemFrame.setItem(new ItemBlock(Block.get(BlockID.AIR))); itemFrame.setItemRotation(0); this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_ITEM_REMOVED); } } else { itemFrame.spawnTo(this); } } break; case ProtocolInfo.LECTERN_UPDATE_PACKET: LecternUpdatePacket lecternUpdatePacket = (LecternUpdatePacket) packet; BlockVector3 blockPosition = lecternUpdatePacket.blockPosition; this.temporalVector.setComponents(blockPosition.x, blockPosition.y, blockPosition.z); if (lecternUpdatePacket.dropBook) { Block blockLectern = this.getLevel().getBlock(temporalVector); if (blockLectern instanceof BlockLectern) { ((BlockLectern) blockLectern).dropBook(this); } } else { BlockEntity blockEntityLectern = this.level.getBlockEntity(this.temporalVector); if (blockEntityLectern instanceof BlockEntityLectern) { BlockEntityLectern lectern = (BlockEntityLectern) blockEntityLectern; LecternPageChangeEvent lecternPageChangeEvent = new LecternPageChangeEvent(this, lectern, lecternUpdatePacket.page); this.server.getPluginManager().callEvent(lecternPageChangeEvent); if (!lecternPageChangeEvent.isCancelled()) { lectern.setRawPage(lecternPageChangeEvent.getNewRawPage()); lectern.spawnToAll(); Block blockLectern = lectern.getBlock(); if (blockLectern instanceof BlockLectern) { ((BlockLectern) blockLectern).executeRedstonePulse(); } } } } break; case ProtocolInfo.MAP_INFO_REQUEST_PACKET: MapInfoRequestPacket pk = (MapInfoRequestPacket) packet; Item mapItem = null; for (Item item1 : this.inventory.getContents().values()) { if (item1 instanceof ItemMap && ((ItemMap) item1).getMapId() == pk.mapId) { mapItem = item1; } } if (mapItem == null) { for (BlockEntity be : this.level.getBlockEntities().values()) { if (be instanceof BlockEntityItemFrame) { BlockEntityItemFrame itemFrame1 = (BlockEntityItemFrame) be; if (itemFrame1.getItem() instanceof ItemMap && ((ItemMap) itemFrame1.getItem()).getMapId() == pk.mapId) { ((ItemMap) itemFrame1.getItem()).sendImage(this); break; } } } } if (mapItem != null) { PlayerMapInfoRequestEvent event; getServer().getPluginManager().callEvent(event = new PlayerMapInfoRequestEvent(this, mapItem)); if (!event.isCancelled()) { ((ItemMap) mapItem).sendImage(this); } } break; case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V1: case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2: case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET: if (!this.isSpectator() || (((LevelSoundEventPacket) packet).sound != LevelSoundEventPacket.SOUND_HIT && ((LevelSoundEventPacket) packet).sound != LevelSoundEventPacket.SOUND_ATTACK_NODAMAGE)) { this.level.addChunkPacket(this.getChunkX(), this.getChunkZ(), packet); } break; case ProtocolInfo.INVENTORY_TRANSACTION_PACKET: if (this.isSpectator()) { this.sendAllInventories(); break; } InventoryTransactionPacket transactionPacket = (InventoryTransactionPacket) packet; List actions = new ArrayList<>(); for (NetworkInventoryAction networkInventoryAction : transactionPacket.actions) { if (craftingType == CRAFTING_STONECUTTER && craftingTransaction != null && networkInventoryAction.sourceType == NetworkInventoryAction.SOURCE_TODO) { networkInventoryAction.windowId = NetworkInventoryAction.SOURCE_TYPE_CRAFTING_RESULT; } else if (craftingType == CRAFTING_CARTOGRAPHY && craftingTransaction != null && transactionPacket.actions.length == 2 && transactionPacket.actions[1].windowId == ContainerIds.UI && networkInventoryAction.inventorySlot == 0) { int slot = transactionPacket.actions[1].inventorySlot; if (slot == 50) { networkInventoryAction.windowId = NetworkInventoryAction.SOURCE_TYPE_CRAFTING_RESULT; } else { networkInventoryAction.inventorySlot = slot - 12; } } InventoryAction a = networkInventoryAction.createInventoryAction(this); if (a == null) { this.getServer().getLogger().debug("Unmatched inventory action from " + this.getName() + ": " + networkInventoryAction); this.sendAllInventories(); break packetswitch; } actions.add(a); } if (transactionPacket.isCraftingPart) { if (this.craftingTransaction == null) { this.craftingTransaction = new CraftingTransaction(this, actions); } else { for (InventoryAction action : actions) { this.craftingTransaction.addAction(action); } } if (this.craftingTransaction.getPrimaryOutput() != null && (this.craftingTransaction.isReadyToExecute() || this.craftingTransaction.canExecute())) { //we get the actions for this in several packets, so we can't execute it until we get the result if (this.craftingTransaction.execute()) { Sound sound = null; switch (craftingType) { case CRAFTING_STONECUTTER: sound = Sound.BLOCK_STONECUTTER_USE; break; case CRAFTING_GRINDSTONE: sound = Sound.BLOCK_GRINDSTONE_USE; break; case CRAFTING_CARTOGRAPHY: sound = Sound.BLOCK_CARTOGRAPHY_TABLE_USE; break; } if (sound != null) { Collection players = level.getChunkPlayers(getChunkX(), getChunkZ()).values(); players.remove(this); if (!players.isEmpty()) { level.addSound(this, sound, 1f, 1f, players); } } } this.craftingTransaction = null; } return; } else if (transactionPacket.isEnchantingPart) { if (this.enchantTransaction == null) { this.enchantTransaction = new EnchantTransaction(this, actions); } else { for (InventoryAction action : actions) { this.enchantTransaction.addAction(action); } } if (this.enchantTransaction.canExecute()) { this.enchantTransaction.execute(); this.enchantTransaction = null; } return; } else if (this.craftingTransaction != null) { if (craftingTransaction.checkForCraftingPart(actions)) { for (InventoryAction action : actions) { craftingTransaction.addAction(action); } return; } else { this.server.getLogger().debug("Got unexpected normal inventory action with incomplete crafting transaction from " + this.getName() + ", refusing to execute crafting"); this.removeAllWindows(false); this.sendAllInventories(); this.craftingTransaction = null; } } else if (this.enchantTransaction != null) { if (enchantTransaction.checkForEnchantPart(actions)) { for (InventoryAction action : actions) { enchantTransaction.addAction(action); } return; } else { this.server.getLogger().debug("Got unexpected normal inventory action with incomplete enchanting transaction from " + this.getName() + ", refusing to execute enchant " + transactionPacket.toString()); this.removeAllWindows(false); this.sendAllInventories(); this.enchantTransaction = null; } } switch (transactionPacket.transactionType) { case InventoryTransactionPacket.TYPE_NORMAL: InventoryTransaction transaction = new InventoryTransaction(this, actions); if (!transaction.execute()) { this.server.getLogger().debug("Failed to execute inventory transaction from " + this.getName() + " with actions: " + Arrays.toString(transactionPacket.actions)); break packetswitch; //oops! } //TODO: fix achievement for getting iron from furnace break packetswitch; case InventoryTransactionPacket.TYPE_MISMATCH: if (transactionPacket.actions.length > 0) { this.server.getLogger().debug("Expected 0 actions for mismatch, got " + transactionPacket.actions.length + ", " + Arrays.toString(transactionPacket.actions)); } this.sendAllInventories(); break packetswitch; case InventoryTransactionPacket.TYPE_USE_ITEM: UseItemData useItemData = (UseItemData) transactionPacket.transactionData; BlockVector3 blockVector = useItemData.blockPos; face = useItemData.face; int type = useItemData.actionType; switch (type) { case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_BLOCK: // Remove if client bug is ever fixed boolean spamBug = (lastRightClickPos != null && System.currentTimeMillis() - lastRightClickTime < 100.0 && blockVector.distanceSquared(lastRightClickPos) < 0.00001); lastRightClickPos = blockVector.asVector3(); lastRightClickTime = System.currentTimeMillis(); if (spamBug && this.getInventory().getItemInHand().getBlockId() == BlockID.AIR) { return; } this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); if (this.canInteract(blockVector.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7)) { if (this.isCreative()) { Item i = inventory.getItemInHand(); if (this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this) != null) { break packetswitch; } } else if (inventory.getItemInHand().equals(useItemData.itemInHand)) { Item i = inventory.getItemInHand(); Item oldItem = i.clone(); //TODO: Implement adventure mode checks if ((i = this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this)) != null) { if (!i.equals(oldItem) || i.getCount() != oldItem.getCount()) { inventory.setItemInHand(i); inventory.sendHeldItem(this.getViewers().values()); } break packetswitch; } } } inventory.sendHeldItem(this); if (blockVector.distanceSquared(this) > 10000) { break packetswitch; } Block target = this.level.getBlock(blockVector.asVector3()); block = target.getSide(face); this.level.sendBlocks(new Player[]{this}, new Block[]{target, block}, UpdateBlockPacket.FLAG_NOGRAPHIC); this.level.sendBlocks(new Player[]{this}, new Block[]{target.getLevelBlockAtLayer(1), block.getLevelBlockAtLayer(1)}, UpdateBlockPacket.FLAG_NOGRAPHIC, 1); break packetswitch; case InventoryTransactionPacket.USE_ITEM_ACTION_BREAK_BLOCK: if (!this.spawned || !this.isAlive()) { break packetswitch; } this.resetCraftingGridType(); Item i = this.getInventory().getItemInHand(); Item oldItem = i.clone(); if (this.canInteract(blockVector.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7) && (i = this.level.useBreakOn(blockVector.asVector3(), face, i, this, true)) != null) { if (this.isSurvival()) { this.getFoodData().updateFoodExpLevel(0.025); if (!i.equals(oldItem) || i.getCount() != oldItem.getCount()) { inventory.setItemInHand(i); inventory.sendHeldItem(this.getViewers().values()); } } break packetswitch; } inventory.sendContents(this); target = this.level.getBlock(blockVector.asVector3()); BlockEntity blockEntity = this.level.getBlockEntity(blockVector.asVector3()); this.level.sendBlocks(new Player[]{this}, new Block[]{target}, UpdateBlockPacket.FLAG_ALL_PRIORITY); inventory.sendHeldItem(this); if (blockEntity instanceof BlockEntitySpawnable) { ((BlockEntitySpawnable) blockEntity).spawnTo(this); } break packetswitch; case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_AIR: Vector3 directionVector = this.getDirectionVector(); if (this.isCreative()) { item = this.inventory.getItemInHand(); } else if (!this.inventory.getItemInHand().equals(useItemData.itemInHand)) { this.inventory.sendHeldItem(this); break packetswitch; } else { item = this.inventory.getItemInHand(); } PlayerInteractEvent interactEvent = new PlayerInteractEvent(this, item, directionVector, face, Action.RIGHT_CLICK_AIR); this.server.getPluginManager().callEvent(interactEvent); if (interactEvent.isCancelled()) { this.inventory.sendHeldItem(this); break packetswitch; } if (item.onClickAir(this, directionVector)) { if (!isCreative()) { this.inventory.setItemInHand(item); } if (!this.isUsingItem()) { this.setUsingItem(true); break packetswitch; } // Used item int ticksUsed = this.server.getTick() - this.startAction; this.setUsingItem(false); if (!item.onUse(this, ticksUsed)) { this.inventory.sendContents(this); } } break packetswitch; default: //unknown break; } break; case InventoryTransactionPacket.TYPE_USE_ITEM_ON_ENTITY: UseItemOnEntityData useItemOnEntityData = (UseItemOnEntityData) transactionPacket.transactionData; Entity target = this.level.getEntity(useItemOnEntityData.entityRuntimeId); if (target == null) { return; } type = useItemOnEntityData.actionType; if (!useItemOnEntityData.itemInHand.equalsExact(this.inventory.getItemInHand())) { this.inventory.sendHeldItem(this); } item = this.inventory.getItemInHand(); switch (type) { case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_INTERACT: PlayerInteractEntityEvent playerInteractEntityEvent = new PlayerInteractEntityEvent(this, target, item, useItemOnEntityData.clickPos); if (this.isSpectator()) playerInteractEntityEvent.setCancelled(); getServer().getPluginManager().callEvent(playerInteractEntityEvent); if (playerInteractEntityEvent.isCancelled()) { break; } if (target.onInteract(this, item, useItemOnEntityData.clickPos) && this.isSurvival()) { if (item.isTool()) { if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { level.addSound(this, Sound.RANDOM_BREAK); item = new ItemBlock(Block.get(BlockID.AIR)); } } else { if (item.count > 1) { item.count--; } else { item = new ItemBlock(Block.get(BlockID.AIR)); } } this.inventory.setItemInHand(item); } break; case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_ATTACK: float itemDamage = item.getAttackDamage(); for (Enchantment enchantment : item.getEnchantments()) { itemDamage += enchantment.getDamageBonus(target); } Map damage = new EnumMap<>(DamageModifier.class); damage.put(DamageModifier.BASE, itemDamage); if (!this.canInteract(target, isCreative() ? 8 : 5)) { break; } else if (target instanceof Player) { if ((((Player) target).getGamemode() & 0x01) > 0) { break; } else if (!this.server.getPropertyBoolean("pvp") || this.server.getDifficulty() == 0) { break; } } EntityDamageByEntityEvent entityDamageByEntityEvent = new EntityDamageByEntityEvent(this, target, DamageCause.ENTITY_ATTACK, damage); if (this.isSpectator()) entityDamageByEntityEvent.setCancelled(); if ((target instanceof Player) && !this.level.getGameRules().getBoolean(GameRule.PVP)) { entityDamageByEntityEvent.setCancelled(); } if (target instanceof EntityLiving) { ((EntityLiving) target).preAttack(this); } try { if (!target.attack(entityDamageByEntityEvent)) { if (item.isTool() && this.isSurvival()) { this.inventory.sendContents(this); } break; } } finally { if (target instanceof EntityLiving) { ((EntityLiving) target).postAttack(this); } } for (Enchantment enchantment : item.getEnchantments()) { enchantment.doPostAttack(this, target); } if (item.isTool() && this.isSurvival()) { if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { level.addSound(this, Sound.RANDOM_BREAK); this.inventory.setItemInHand(new ItemBlock(Block.get(BlockID.AIR))); } else { this.inventory.setItemInHand(item); } } return; default: break; //unknown } break; case InventoryTransactionPacket.TYPE_RELEASE_ITEM: if (this.isSpectator()) { this.sendAllInventories(); break packetswitch; } ReleaseItemData releaseItemData = (ReleaseItemData) transactionPacket.transactionData; try { type = releaseItemData.actionType; switch (type) { case InventoryTransactionPacket.RELEASE_ITEM_ACTION_RELEASE: if (this.isUsingItem()) { item = this.inventory.getItemInHand(); int ticksUsed = this.server.getTick() - this.startAction; if (!item.onRelease(this, ticksUsed)) { this.inventory.sendContents(this); } this.setUsingItem(false); } else { this.inventory.sendContents(this); } return; case InventoryTransactionPacket.RELEASE_ITEM_ACTION_CONSUME: log.debug("Unexpected release item action consume from {}", this::getName); return; default: break; } } finally { this.setUsingItem(false); } break; default: this.inventory.sendContents(this); break; } break; case ProtocolInfo.PLAYER_HOTBAR_PACKET: PlayerHotbarPacket hotbarPacket = (PlayerHotbarPacket) packet; if (hotbarPacket.windowId != ContainerIds.INVENTORY) { return; //In PE this should never happen } this.inventory.equipItem(hotbarPacket.selectedHotbarSlot); break; case ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET: PlayerServerSettingsRequestEvent settingsRequestEvent = new PlayerServerSettingsRequestEvent(this, new HashMap<>(this.serverSettings)); this.getServer().getPluginManager().callEvent(settingsRequestEvent); if (!settingsRequestEvent.isCancelled()) { settingsRequestEvent.getSettings().forEach((id, window) -> { ServerSettingsResponsePacket re = new ServerSettingsResponsePacket(); re.formId = id; re.data = window.getJSONData(); this.dataPacket(re); }); } break; case ProtocolInfo.RESPAWN_PACKET: if (this.isAlive()) { break; } RespawnPacket respawnPacket = (RespawnPacket) packet; if (respawnPacket.respawnState == RespawnPacket.STATE_CLIENT_READY_TO_SPAWN) { RespawnPacket respawn1 = new RespawnPacket(); respawn1.x = (float) this.getX(); respawn1.y = (float) this.getY(); respawn1.z = (float) this.getZ(); respawn1.respawnState = RespawnPacket.STATE_READY_TO_SPAWN; this.dataPacket(respawn1); } break; case ProtocolInfo.BOOK_EDIT_PACKET: BookEditPacket bookEditPacket = (BookEditPacket) packet; Item oldBook = this.inventory.getItem(bookEditPacket.inventorySlot); if (oldBook.getId() != Item.BOOK_AND_QUILL) { return; } if (bookEditPacket.text.length() > 256) { return; } Item newBook = oldBook.clone(); boolean success; switch (bookEditPacket.action) { case REPLACE_PAGE: success = ((ItemBookAndQuill) newBook).setPageText(bookEditPacket.pageNumber, bookEditPacket.text); break; case ADD_PAGE: success = ((ItemBookAndQuill) newBook).insertPage(bookEditPacket.pageNumber, bookEditPacket.text); break; case DELETE_PAGE: success = ((ItemBookAndQuill) newBook).deletePage(bookEditPacket.pageNumber); break; case SWAP_PAGES: success = ((ItemBookAndQuill) newBook).swapPages(bookEditPacket.pageNumber, bookEditPacket.secondaryPageNumber); break; case SIGN_BOOK: newBook = Item.get(Item.WRITTEN_BOOK, 0, 1, oldBook.getCompoundTag()); success = ((ItemBookWritten) newBook).signBook(bookEditPacket.title, bookEditPacket.author, bookEditPacket.xuid, ItemBookWritten.GENERATION_ORIGINAL); break; default: return; } if (success) { PlayerEditBookEvent editBookEvent = new PlayerEditBookEvent(this, oldBook, newBook, bookEditPacket.action); this.server.getPluginManager().callEvent(editBookEvent); if (!editBookEvent.isCancelled()) { this.inventory.setItem(bookEditPacket.inventorySlot, editBookEvent.getNewBook()); } } break; case ProtocolInfo.POS_TRACKING_CLIENT_REQUEST_PACKET: PositionTrackingDBClientRequestPacket posTrackReq = (PositionTrackingDBClientRequestPacket) packet; try { PositionTracking positionTracking = this.server.getPositionTrackingService().startTracking(this, posTrackReq.getTrackingId(), true); if (positionTracking != null) { break; } } catch (IOException e) { log.warn("Failed to track the trackingHandler "+posTrackReq.getTrackingId(), e); } PositionTrackingDBServerBroadcastPacket notFound = new PositionTrackingDBServerBroadcastPacket(); notFound.setAction(PositionTrackingDBServerBroadcastPacket.Action.NOT_FOUND); notFound.setTrackingId(posTrackReq.getTrackingId()); dataPacket(notFound); break; default: break; } } } /** * Sends a chat message as this player. If the message begins with a / (forward-slash) it will be treated * as a command. * @param message message to send * @return successful */ public boolean chat(String message) { if (!this.spawned || !this.isAlive()) { return false; } this.resetCraftingGridType(); this.craftingType = CRAFTING_SMALL; if (this.removeFormat) { message = TextFormat.clean(message, true); } for (String msg : message.split("\n")) { if (!msg.trim().isEmpty() && msg.length() <= 255 && this.messageCounter-- > 0) { PlayerChatEvent chatEvent = new PlayerChatEvent(this, msg); this.server.getPluginManager().callEvent(chatEvent); if (!chatEvent.isCancelled()) { this.server.broadcastMessage(this.getServer().getLanguage().translateString(chatEvent.getFormat(), new String[]{chatEvent.getPlayer().getDisplayName(), chatEvent.getMessage()}), chatEvent.getRecipients()); } } } return true; } public boolean kick() { return this.kick(""); } public boolean kick(String reason, boolean isAdmin) { return this.kick(PlayerKickEvent.Reason.UNKNOWN, reason, isAdmin); } public boolean kick(String reason) { return kick(PlayerKickEvent.Reason.UNKNOWN, reason); } public boolean kick(PlayerKickEvent.Reason reason) { return this.kick(reason, true); } public boolean kick(PlayerKickEvent.Reason reason, String reasonString) { return this.kick(reason, reasonString, true); } public boolean kick(PlayerKickEvent.Reason reason, boolean isAdmin) { return this.kick(reason, reason.toString(), isAdmin); } public boolean kick(PlayerKickEvent.Reason reason, String reasonString, boolean isAdmin) { PlayerKickEvent ev; this.server.getPluginManager().callEvent(ev = new PlayerKickEvent(this, reason, this.getLeaveMessage())); if (!ev.isCancelled()) { String message; if (isAdmin) { if (!this.isBanned()) { message = "Kicked by admin." + (!reasonString.isEmpty() ? " Reason: " + reasonString : ""); } else { message = reasonString; } } else { if (reasonString.isEmpty()) { message = "disconnectionScreen.noReason"; } else { message = reasonString; } } this.close(ev.getQuitMessage(), message); return true; } return false; } public void setViewDistance(int distance) { this.chunkRadius = distance; ChunkRadiusUpdatedPacket pk = new ChunkRadiusUpdatedPacket(); pk.radius = distance; this.dataPacket(pk); } public int getViewDistance() { return this.chunkRadius; } @Override public void sendMessage(String message) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_RAW; pk.message = this.server.getLanguage().translateString(message); this.dataPacket(pk); } @Override public void sendMessage(TextContainer message) { if (message instanceof TranslationContainer) { this.sendTranslation(message.getText(), ((TranslationContainer) message).getParameters()); return; } this.sendMessage(message.getText()); } public void sendTranslation(String message) { this.sendTranslation(message, EmptyArrays.EMPTY_STRINGS); } public void sendTranslation(String message, String[] parameters) { TextPacket pk = new TextPacket(); if (!this.server.isLanguageForced()) { pk.type = TextPacket.TYPE_TRANSLATION; pk.message = this.server.getLanguage().translateString(message, parameters, "nukkit."); for (int i = 0; i < parameters.length; i++) { parameters[i] = this.server.getLanguage().translateString(parameters[i], parameters, "nukkit."); } pk.parameters = parameters; } else { pk.type = TextPacket.TYPE_RAW; pk.message = this.server.getLanguage().translateString(message, parameters); } this.dataPacket(pk); } public void sendChat(String message) { this.sendChat("", message); } public void sendChat(String source, String message) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_CHAT; pk.source = source; pk.message = this.server.getLanguage().translateString(message); this.dataPacket(pk); } public void sendPopup(String message) { this.sendPopup(message, ""); } public void sendPopup(String message, String subtitle) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_POPUP; pk.message = message; this.dataPacket(pk); } public void sendTip(String message) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_TIP; pk.message = message; this.dataPacket(pk); } public void clearTitle() { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_CLEAR; this.dataPacket(pk); } /** * Resets both title animation times and subtitle for the next shown title */ public void resetTitleSettings() { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_RESET; this.dataPacket(pk); } public void setSubtitle(String subtitle) { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_SUBTITLE; pk.text = subtitle; this.dataPacket(pk); } public void setTitleAnimationTimes(int fadein, int duration, int fadeout) { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_ANIMATION_TIMES; pk.fadeInTime = fadein; pk.stayTime = duration; pk.fadeOutTime = fadeout; this.dataPacket(pk); } private void setTitle(String text) { SetTitlePacket packet = new SetTitlePacket(); packet.text = text; packet.type = SetTitlePacket.TYPE_TITLE; this.dataPacket(packet); } public void sendTitle(String title) { this.sendTitle(title, null, 20, 20, 5); } public void sendTitle(String title, String subtitle) { this.sendTitle(title, subtitle, 20, 20, 5); } public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut) { this.setTitleAnimationTimes(fadeIn, stay, fadeOut); if (!Strings.isNullOrEmpty(subtitle)) { this.setSubtitle(subtitle); } // title won't send if an empty string is used. this.setTitle(Strings.isNullOrEmpty(title) ? " " : title); } public void sendActionBar(String title) { this.sendActionBar(title, 1, 0, 1); } public void sendActionBar(String title, int fadein, int duration, int fadeout) { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_ACTION_BAR; pk.text = title; pk.fadeInTime = fadein; pk.stayTime = duration; pk.fadeOutTime = fadeout; this.dataPacket(pk); } @Override public void close() { this.close(""); } public void close(String message) { this.close(message, "generic"); } public void close(String message, String reason) { this.close(message, reason, true); } public void close(String message, String reason, boolean notify) { this.close(new TextContainer(message), reason, notify); } public void close(TextContainer message) { this.close(message, "generic"); } public void close(TextContainer message, String reason) { this.close(message, reason, true); } public void close(TextContainer message, String reason, boolean notify) { if (this.connected && !this.closed) { if (notify && reason.length() > 0) { DisconnectPacket pk = new DisconnectPacket(); pk.message = reason; this.directDataPacket(pk); } this.connected = false; PlayerQuitEvent ev = null; if (this.getName() != null && this.getName().length() > 0) { this.server.getPluginManager().callEvent(ev = new PlayerQuitEvent(this, message, true, reason)); if (this.fishing != null) { this.stopFishing(false); } } // Close the temporary windows first, so they have chance to change all inventories before being disposed this.removeAllWindows(false); resetCraftingGridType(); if (ev != null && this.loggedIn && ev.getAutoSave()) { this.save(); } for (Player player : new ArrayList<>(this.server.getOnlinePlayers().values())) { if (!player.canSee(this)) { player.showPlayer(this); } } this.hiddenPlayers.clear(); this.removeAllWindows(true); for (long index : new ArrayList<>(this.usedChunks.keySet())) { int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); this.level.unregisterChunkLoader(this, chunkX, chunkZ); this.usedChunks.remove(index); for (Entity entity : level.getChunkEntities(chunkX, chunkZ).values()) { if (entity != this) { entity.getViewers().remove(getLoaderId()); } } } super.close(); this.interfaz.close(this, notify ? reason : ""); if (this.loggedIn) { this.server.removeOnlinePlayer(this); } this.loggedIn = false; if (ev != null && !Objects.equals(this.username, "") && this.spawned && !Objects.equals(ev.getQuitMessage().toString(), "")) { this.server.broadcastMessage(ev.getQuitMessage()); } this.server.getPluginManager().unsubscribeFromPermission(Server.BROADCAST_CHANNEL_USERS, this); this.spawned = false; this.server.getLogger().info(this.getServer().getLanguage().translateString("nukkit.player.logOut", TextFormat.AQUA + (this.getName() == null ? "" : this.getName()) + TextFormat.WHITE, this.getAddress(), String.valueOf(this.getPort()), this.getServer().getLanguage().translateString(reason))); this.windows.clear(); this.usedChunks.clear(); this.loadQueue.clear(); this.hasSpawned.clear(); this.spawnPosition = null; if (this.riding instanceof EntityRideable) { this.riding.passengers.remove(this); } this.riding = null; } if (this.perm != null) { this.perm.clearPermissions(); this.perm = null; } if (this.inventory != null) { this.inventory = null; } this.chunk = null; this.server.removePlayer(this); } public void save() { this.save(false); } public void save(boolean async) { if (this.closed) { throw new IllegalStateException("Tried to save closed player"); } super.saveNBT(); if (this.level != null) { this.namedTag.putString("Level", this.level.getFolderName()); if (this.spawnPosition != null && this.spawnPosition.getLevel() != null) { this.namedTag.putString("SpawnLevel", this.spawnPosition.getLevel().getFolderName()); this.namedTag.putInt("SpawnX", (int) this.spawnPosition.x); this.namedTag.putInt("SpawnY", (int) this.spawnPosition.y); this.namedTag.putInt("SpawnZ", (int) this.spawnPosition.z); } CompoundTag achievements = new CompoundTag(); for (String achievement : this.achievements) { achievements.putByte(achievement, 1); } this.namedTag.putCompound("Achievements", achievements); this.namedTag.putInt("playerGameType", this.gamemode); this.namedTag.putLong("lastPlayed", System.currentTimeMillis() / 1000); this.namedTag.putString("lastIP", this.getAddress()); this.namedTag.putInt("EXP", this.getExperience()); this.namedTag.putInt("expLevel", this.getExperienceLevel()); this.namedTag.putInt("foodLevel", this.getFoodData().getLevel()); this.namedTag.putFloat("foodSaturationLevel", this.getFoodData().getFoodSaturationLevel()); this.namedTag.putInt("TimeSinceRest", this.timeSinceRest); if (!this.username.isEmpty() && this.namedTag != null) { this.server.saveOfflinePlayerData(this.uuid, this.namedTag, async); } } } public String getName() { return this.username; } @Override public void kill() { if (!this.spawned) { return; } boolean showMessages = this.level.getGameRules().getBoolean(GameRule.SHOW_DEATH_MESSAGE); String message = ""; List params = new ArrayList<>(); EntityDamageEvent cause = this.getLastDamageCause(); if (showMessages) { params.add(this.getDisplayName()); switch (cause == null ? DamageCause.CUSTOM : cause.getCause()) { case ENTITY_ATTACK: if (cause instanceof EntityDamageByEntityEvent) { Entity e = ((EntityDamageByEntityEvent) cause).getDamager(); killer = e; if (e instanceof Player) { message = "death.attack.player"; params.add(((Player) e).getDisplayName()); break; } else if (e instanceof EntityLiving) { message = "death.attack.mob"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; } else { params.add("Unknown"); } } break; case PROJECTILE: if (cause instanceof EntityDamageByEntityEvent) { Entity e = ((EntityDamageByEntityEvent) cause).getDamager(); killer = e; if (e instanceof Player) { message = "death.attack.arrow"; params.add(((Player) e).getDisplayName()); } else if (e instanceof EntityLiving) { message = "death.attack.arrow"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; } else { params.add("Unknown"); } } break; case VOID: message = "death.attack.outOfWorld"; break; case FALL: if (cause.getFinalDamage() > 2) { message = "death.fell.accident.generic"; break; } message = "death.attack.fall"; break; case SUFFOCATION: message = "death.attack.inWall"; break; case LAVA: Block block = this.level.getBlock(new Vector3(this.x, this.y - 1, this.z)); if (block.getId() == Block.MAGMA) { message = "death.attack.lava.magma"; break; } message = "death.attack.lava"; break; case FIRE: message = "death.attack.onFire"; break; case FIRE_TICK: message = "death.attack.inFire"; break; case DROWNING: message = "death.attack.drown"; break; case CONTACT: if (cause instanceof EntityDamageByBlockEvent) { if (((EntityDamageByBlockEvent) cause).getDamager().getId() == Block.CACTUS) { message = "death.attack.cactus"; } } break; case BLOCK_EXPLOSION: case ENTITY_EXPLOSION: if (cause instanceof EntityDamageByEntityEvent) { Entity e = ((EntityDamageByEntityEvent) cause).getDamager(); killer = e; if (e instanceof Player) { message = "death.attack.explosion.player"; params.add(((Player) e).getDisplayName()); } else if (e instanceof EntityLiving) { message = "death.attack.explosion.player"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; } else { message = "death.attack.explosion"; } } else { message = "death.attack.explosion"; } break; case MAGIC: message = "death.attack.magic"; break; case LIGHTNING: message = "death.attack.lightningBolt"; break; case HUNGER: message = "death.attack.starve"; break; default: message = "death.attack.generic"; break; } } PlayerDeathEvent ev = new PlayerDeathEvent(this, this.getDrops(), new TranslationContainer(message, params.toArray(EmptyArrays.EMPTY_STRINGS)), this.expLevel); ev.setKeepExperience(this.level.gameRules.getBoolean(GameRule.KEEP_INVENTORY)); ev.setKeepInventory(ev.getKeepExperience()); // Note for diff comparison: Totem code was moved to Entity this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { if (this.fishing != null) { this.stopFishing(false); } this.health = 0; this.extinguish(); this.scheduleUpdate(); if (!ev.getKeepInventory() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { for (Item item : ev.getDrops()) { if (!item.hasEnchantment(Enchantment.ID_VANISHING_CURSE)) { this.level.dropItem(this, item, null, true, 40); } } if (this.inventory != null) { this.inventory.clearAll(); } if (this.offhandInventory != null) { this.offhandInventory.clearAll(); } } if (!ev.getKeepExperience() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { if (this.isSurvival() || this.isAdventure()) { int exp = ev.getExperience() * 7; if (exp > 100) exp = 100; this.getLevel().dropExpOrb(this, exp); } this.setExperience(0, 0); } this.timeSinceRest = 0; if (showMessages && !ev.getDeathMessage().toString().isEmpty()) { this.server.broadcast(ev.getDeathMessage(), Server.BROADCAST_CHANNEL_USERS); } RespawnPacket pk = new RespawnPacket(); Position pos = this.getSpawn(); pk.x = (float) pos.x; pk.y = (float) pos.y; pk.z = (float) pos.z; pk.respawnState = RespawnPacket.STATE_SEARCHING_FOR_SPAWN; this.dataPacket(pk); } } protected void respawn() { if (this.server.isHardcore()) { this.setBanned(true); return; } this.craftingType = CRAFTING_SMALL; this.resetCraftingGridType(); PlayerRespawnEvent playerRespawnEvent = new PlayerRespawnEvent(this, this.getSpawn()); this.server.getPluginManager().callEvent(playerRespawnEvent); Position respawnPos = playerRespawnEvent.getRespawnPosition(); this.sendExperience(); this.sendExperienceLevel(); this.setSprinting(false); this.setSneaking(false); this.setDataProperty(new ShortEntityData(Player.DATA_AIR, 400), false); this.deadTicks = 0; this.noDamageTicks = 60; this.removeAllEffects(); this.setHealth(this.getMaxHealth()); this.getFoodData().setLevel(20, 20); this.sendData(this); this.setMovementSpeed(DEFAULT_SPEED); this.getAdventureSettings().update(); this.inventory.sendContents(this); this.inventory.sendArmorContents(this); this.offhandInventory.sendContents(this); this.teleport(respawnPos, null); this.spawnToAll(); this.scheduleUpdate(); } @Override public void setHealth(float health) { if (health < 1) { health = 0; } super.setHealth(health); //TODO: Remove it in future! This a hack to solve the client-side absorption bug! WFT Mojang (Half a yellow heart cannot be shown, we can test it in local gaming) Attribute attr = Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getAbsorption() % 2 != 0 ? this.getMaxHealth() + 1 : this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0); if (this.spawned) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); pk.entries = new Attribute[]{attr}; pk.entityId = this.id; this.dataPacket(pk); } } @Override public void setMaxHealth(int maxHealth) { super.setMaxHealth(maxHealth); Attribute attr = Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getAbsorption() % 2 != 0 ? this.getMaxHealth() + 1 : this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0); if (this.spawned) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); pk.entries = new Attribute[]{attr}; pk.entityId = this.id; this.dataPacket(pk); } } public int getExperience() { return this.exp; } public int getExperienceLevel() { return this.expLevel; } public void addExperience(int add) { addExperience(add, false); } public void addExperience(int add, boolean playLevelUpSound) { if (add == 0) return; int now = this.getExperience(); int added = now + add; int level = this.getExperienceLevel(); int most = calculateRequireExperience(level); while (added >= most) { //Level Up! added = added - most; level++; most = calculateRequireExperience(level); } this.setExperience(added, level, playLevelUpSound); } public static int calculateRequireExperience(int level) { if (level >= 30) { return 112 + (level - 30) * 9; } else if (level >= 15) { return 37 + (level - 15) * 5; } else { return 7 + level * 2; } } public void setExperience(int exp) { setExperience(exp, this.getExperienceLevel()); } public void setExperience(int exp, int level) { setExperience(exp, level, false); } //todo something on performance, lots of exp orbs then lots of packets, could crash client public void setExperience(int exp, int level, boolean playLevelUpSound) { int levelBefore = this.expLevel; this.exp = exp; this.expLevel = level; this.sendExperienceLevel(level); this.sendExperience(exp); if (playLevelUpSound && levelBefore < level && levelBefore / 5 != level / 5 && this.lastPlayerdLevelUpSoundTime < this.age - 100) { this.lastPlayerdLevelUpSoundTime = this.age; this.level.addLevelSoundEvent( this, LevelSoundEventPacketV2.SOUND_LEVELUP, Math.min(7, level / 5) << 28, "", false, false ); } } public void sendExperience() { sendExperience(this.getExperience()); } public void sendExperience(int exp) { if (this.spawned) { float percent = ((float) exp) / calculateRequireExperience(this.getExperienceLevel()); percent = Math.max(0f, Math.min(1f, percent)); this.setAttribute(Attribute.getAttribute(Attribute.EXPERIENCE).setValue(percent)); } } public void sendExperienceLevel() { sendExperienceLevel(this.getExperienceLevel()); } public void sendExperienceLevel(int level) { if (this.spawned) { this.setAttribute(Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(level)); } } public void setAttribute(Attribute attribute) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); pk.entries = new Attribute[]{attribute}; pk.entityId = this.id; this.dataPacket(pk); } @Override public void setMovementSpeed(float speed) { setMovementSpeed(speed, true); } public void setMovementSpeed(float speed, boolean send) { super.setMovementSpeed(speed); if (this.spawned && send) { Attribute attribute = Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(speed); this.setAttribute(attribute); } } public Entity getKiller() { return killer; } @Override public boolean attack(EntityDamageEvent source) { if (!this.isAlive()) { return false; } if (this.isSpectator() || (this.isCreative() && source.getCause() != DamageCause.SUICIDE)) { //source.setCancelled(); return false; } else if (this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && source.getCause() == DamageCause.FALL) { //source.setCancelled(); return false; } else if (source.getCause() == DamageCause.FALL) { if (this.getLevel().getBlock(this.getPosition().floor().add(0.5, -1, 0.5)).getId() == Block.SLIME_BLOCK) { if (!this.isSneaking()) { //source.setCancelled(); this.resetFallDistance(); return false; } } } if (super.attack(source)) { //!source.isCancelled() if (this.getLastDamageCause() == source && this.spawned) { if (source instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); if (damager instanceof Player) { ((Player) damager).getFoodData().updateFoodExpLevel(0.3); } } EntityEventPacket pk = new EntityEventPacket(); pk.eid = this.id; pk.event = EntityEventPacket.HURT_ANIMATION; this.dataPacket(pk); } return true; } else { return false; } } /** * Drops an item on the ground in front of the player. Returns if the item drop was successful. * * @param item to drop * @return bool if the item was dropped or if the item was null */ public boolean dropItem(Item item) { if (!this.spawned || !this.isAlive()) { return false; } if (item.isNull()) { this.server.getLogger().debug(this.getName() + " attempted to drop a null item (" + item + ")"); return true; } Vector3 motion = this.getDirectionVector().multiply(0.4); this.level.dropItem(this.add(0, 1.3, 0), item, motion, 40); this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); return true; } /** * Drops an item on the ground in front of the player. Returns the dropped item. * * @param item to drop * @return EntityItem if the item was dropped or null if the item was null */ @Since("1.3.2.0-PN") @Nullable public EntityItem dropAndGetItem(@Nonnull Item item) { if (!this.spawned || !this.isAlive()) { return null; } if (item.isNull()) { this.server.getLogger().debug(this.getName() + " attempted to drop a null item (" + item + ")"); return null; } Vector3 motion = this.getDirectionVector().multiply(0.4); this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); return this.level.dropAndGetItem(this.add(0, 1.3, 0), item, motion, 40); } public void sendPosition(Vector3 pos) { this.sendPosition(pos, this.yaw); } public void sendPosition(Vector3 pos, double yaw) { this.sendPosition(pos, yaw, this.pitch); } public void sendPosition(Vector3 pos, double yaw, double pitch) { this.sendPosition(pos, yaw, pitch, MovePlayerPacket.MODE_NORMAL); } public void sendPosition(Vector3 pos, double yaw, double pitch, int mode) { this.sendPosition(pos, yaw, pitch, mode, null); } public void sendPosition(Vector3 pos, double yaw, double pitch, int mode, Player[] targets) { MovePlayerPacket pk = new MovePlayerPacket(); pk.eid = this.getId(); pk.x = (float) pos.x; pk.y = (float) (pos.y + this.getEyeHeight()); pk.z = (float) pos.z; pk.headYaw = (float) yaw; pk.pitch = (float) pitch; pk.yaw = (float) yaw; pk.mode = mode; if (targets != null) { Server.broadcastPacket(targets, pk); } else { pk.eid = this.id; this.dataPacket(pk); } } @Override protected void checkChunks() { if (this.chunk == null || (this.chunk.getX() != ((int) this.x >> 4) || this.chunk.getZ() != ((int) this.z >> 4))) { if (this.chunk != null) { this.chunk.removeEntity(this); } this.chunk = this.level.getChunk((int) this.x >> 4, (int) this.z >> 4, true); if (!this.justCreated) { Map newChunk = this.level.getChunkPlayers((int) this.x >> 4, (int) this.z >> 4); newChunk.remove(this.getLoaderId()); //List reload = new ArrayList<>(); for (Player player : new ArrayList<>(this.hasSpawned.values())) { if (!newChunk.containsKey(player.getLoaderId())) { this.despawnFrom(player); } else { newChunk.remove(player.getLoaderId()); //reload.add(player); } } for (Player player : newChunk.values()) { this.spawnTo(player); } } if (this.chunk == null) { return; } this.chunk.addEntity(this); } } protected boolean checkTeleportPosition() { if (this.teleportPosition != null) { int chunkX = (int) this.teleportPosition.x >> 4; int chunkZ = (int) this.teleportPosition.z >> 4; for (int X = -1; X <= 1; ++X) { for (int Z = -1; Z <= 1; ++Z) { long index = Level.chunkHash(chunkX + X, chunkZ + Z); if (!this.usedChunks.containsKey(index) || !this.usedChunks.get(index)) { return false; } } } this.spawnToAll(); this.forceMovement = this.teleportPosition; this.teleportPosition = null; return true; } return false; } protected void sendPlayStatus(int status) { sendPlayStatus(status, false); } protected void sendPlayStatus(int status, boolean immediate) { PlayStatusPacket pk = new PlayStatusPacket(); pk.status = status; if (immediate) { this.directDataPacket(pk); } else { this.dataPacket(pk); } } @Override public boolean teleport(Location location, TeleportCause cause) { if (!this.isOnline()) { return false; } Location from = this.getLocation(); Location to = location; if (cause != null) { PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); this.server.getPluginManager().callEvent(event); if (event.isCancelled()) return false; to = event.getTo(); } //TODO Remove it! A hack to solve the client-side teleporting bug! (inside into the block) if (super.teleport(to.getY() == to.getFloorY() ? to.add(0, 0.00001, 0) : to, null)) { // null to prevent fire of duplicate EntityTeleportEvent this.removeAllWindows(); this.teleportPosition = new Vector3(this.x, this.y, this.z); this.forceMovement = this.teleportPosition; this.yaw = to.yaw; this.pitch = to.pitch; this.sendPosition(this, to.yaw, to.pitch, MovePlayerPacket.MODE_TELEPORT); this.checkTeleportPosition(); this.resetFallDistance(); this.nextChunkOrderRun = 0; this.newPosition = null; //DummyBossBar this.getDummyBossBars().values().forEach(DummyBossBar::reshow); //Weather this.getLevel().sendWeather(this); //Update time this.getLevel().sendTime(this); updateTrackingPositions(true); return true; } return false; } protected void forceSendEmptyChunks() { int chunkPositionX = this.getFloorX() >> 4; int chunkPositionZ = this.getFloorZ() >> 4; for (int x = -chunkRadius; x < chunkRadius; x++) { for (int z = -chunkRadius; z < chunkRadius; z++) { LevelChunkPacket chunk = new LevelChunkPacket(); chunk.chunkX = chunkPositionX + x; chunk.chunkZ = chunkPositionZ + z; chunk.data = EmptyArrays.EMPTY_BYTES; this.dataPacket(chunk); } } } public void teleportImmediate(Location location) { this.teleportImmediate(location, TeleportCause.PLUGIN); } public void teleportImmediate(Location location, TeleportCause cause) { Location from = this.getLocation(); if (super.teleport(location, cause)) { for (Inventory window : new ArrayList<>(this.windows.keySet())) { if (window == this.inventory) { continue; } this.removeWindow(window); } if (from.getLevel().getId() != location.getLevel().getId()) { //Different level, update compass position SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); pk.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; Position spawn = location.getLevel().getSpawnLocation(); pk.x = spawn.getFloorX(); pk.y = spawn.getFloorY(); pk.z = spawn.getFloorZ(); pk.dimension = spawn.getLevel().getDimension(); dataPacket(pk); } this.forceMovement = new Vector3(this.x, this.y, this.z); this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_RESET); this.resetFallDistance(); this.orderChunks(); this.nextChunkOrderRun = 0; this.newPosition = null; //Weather this.getLevel().sendWeather(this); //Update time this.getLevel().sendTime(this); updateTrackingPositions(true); } } /** * Shows a new FormWindow to the player * You can find out FormWindow result by listening to PlayerFormRespondedEvent * * @param window to show * @return form id to use in {@link PlayerFormRespondedEvent} */ public int showFormWindow(FormWindow window) { return showFormWindow(window, this.formWindowCount++); } /** * Shows a new FormWindow to the player * You can find out FormWindow result by listening to PlayerFormRespondedEvent * * @param window to show * @param id form id * @return form id to use in {@link PlayerFormRespondedEvent} */ public int showFormWindow(FormWindow window, int id) { ModalFormRequestPacket packet = new ModalFormRequestPacket(); packet.formId = id; packet.data = window.getJSONData(); this.formWindows.put(packet.formId, window); this.dataPacket(packet); return id; } /** * Shows a new setting page in game settings * You can find out settings result by listening to PlayerFormRespondedEvent * * @param window to show on settings page * @return form id to use in {@link PlayerFormRespondedEvent} */ public int addServerSettings(FormWindow window) { int id = this.formWindowCount++; this.serverSettings.put(id, window); return id; } /** * Creates and sends a BossBar to the player * * @param text The BossBar message * @param length The BossBar percentage * @return bossBarId The BossBar ID, you should store it if you want to remove or update the BossBar later */ @Deprecated public long createBossBar(String text, int length) { DummyBossBar bossBar = new DummyBossBar.Builder(this).text(text).length(length).build(); return this.createBossBar(bossBar); } /** * Creates and sends a BossBar to the player * * @param dummyBossBar DummyBossBar Object (Instantiate it by the Class Builder) * @return bossBarId The BossBar ID, you should store it if you want to remove or update the BossBar later * @see DummyBossBar.Builder */ public long createBossBar(DummyBossBar dummyBossBar) { this.dummyBossBars.put(dummyBossBar.getBossBarId(), dummyBossBar); dummyBossBar.create(); return dummyBossBar.getBossBarId(); } /** * Get a DummyBossBar object * * @param bossBarId The BossBar ID * @return DummyBossBar object * @see DummyBossBar#setText(String) Set BossBar text * @see DummyBossBar#setLength(float) Set BossBar length * @see DummyBossBar#setColor(BlockColor) Set BossBar color */ public DummyBossBar getDummyBossBar(long bossBarId) { return this.dummyBossBars.getOrDefault(bossBarId, null); } /** * Get all DummyBossBar objects * * @return DummyBossBars Map */ public Map getDummyBossBars() { return dummyBossBars; } /** * Updates a BossBar * * @param text The new BossBar message * @param length The new BossBar length * @param bossBarId The BossBar ID */ @Deprecated public void updateBossBar(String text, int length, long bossBarId) { if (this.dummyBossBars.containsKey(bossBarId)) { DummyBossBar bossBar = this.dummyBossBars.get(bossBarId); bossBar.setText(text); bossBar.setLength(length); } } /** * Removes a BossBar * * @param bossBarId The BossBar ID */ public void removeBossBar(long bossBarId) { if (this.dummyBossBars.containsKey(bossBarId)) { this.dummyBossBars.get(bossBarId).destroy(); this.dummyBossBars.remove(bossBarId); } } public int getWindowId(Inventory inventory) { if (this.windows.containsKey(inventory)) { return this.windows.get(inventory); } return -1; } public Inventory getWindowById(int id) { return this.windowIndex.get(id); } public int addWindow(Inventory inventory) { return this.addWindow(inventory, null); } public int addWindow(Inventory inventory, Integer forceId) { return addWindow(inventory, forceId, false); } public int addWindow(Inventory inventory, Integer forceId, boolean isPermanent) { return addWindow(inventory, forceId, isPermanent, false); } public int addWindow(Inventory inventory, Integer forceId, boolean isPermanent, boolean alwaysOpen) { if (this.windows.containsKey(inventory)) { return this.windows.get(inventory); } int cnt; if (forceId == null) { this.windowCnt = cnt = Math.max(4, ++this.windowCnt % 99); } else { cnt = forceId; } this.windows.forcePut(inventory, cnt); if (isPermanent) { this.permanentWindows.add(cnt); } if (this.spawned && inventory.open(this)) { if (!isPermanent) { updateTrackingPositions(true); } return cnt; } else if (!alwaysOpen) { this.removeWindow(inventory); return -1; } else { inventory.getViewers().add(this); } if (!isPermanent) { updateTrackingPositions(true); } return cnt; } public Optional getTopWindow() { for (Entry entry : this.windows.entrySet()) { if (!this.permanentWindows.contains(entry.getValue())) { return Optional.of(entry.getKey()); } } return Optional.empty(); } public void removeWindow(Inventory inventory) { inventory.close(this); if (!this.permanentWindows.contains(this.getWindowId(inventory))) { this.windows.remove(inventory); updateTrackingPositions(true); } } public void sendAllInventories() { for (Inventory inv : this.windows.keySet()) { inv.sendContents(this); if (inv instanceof PlayerInventory) { ((PlayerInventory) inv).sendArmorContents(this); } } } protected void addDefaultWindows() { this.addWindow(this.getInventory(), ContainerIds.INVENTORY, true, true); this.playerUIInventory = new PlayerUIInventory(this); this.addWindow(this.playerUIInventory, ContainerIds.UI, true); this.addWindow(this.offhandInventory, ContainerIds.OFFHAND, true, true); this.craftingGrid = this.playerUIInventory.getCraftingGrid(); this.addWindow(this.craftingGrid, ContainerIds.NONE); //TODO: more windows } public PlayerUIInventory getUIInventory() { return playerUIInventory; } public PlayerCursorInventory getCursorInventory() { return this.playerUIInventory.getCursorInventory(); } public CraftingGrid getCraftingGrid() { return this.craftingGrid; } public void setCraftingGrid(CraftingGrid grid) { this.craftingGrid = grid; this.addWindow(grid, ContainerIds.NONE); } public void resetCraftingGridType() { if (this.craftingGrid != null) { Item[] drops = this.inventory.addItem(this.craftingGrid.getContents().values().toArray(Item.EMPTY_ARRAY)); if (drops.length > 0) { for (Item drop : drops) { this.dropItem(drop); } } drops = this.inventory.addItem(this.getCursorInventory().getItem(0)); if (drops.length > 0) { for (Item drop : drops) { this.dropItem(drop); } } this.playerUIInventory.clearAll(); if (this.craftingGrid instanceof BigCraftingGrid) { this.craftingGrid = this.playerUIInventory.getCraftingGrid(); this.addWindow(this.craftingGrid, ContainerIds.NONE); // // ContainerClosePacket pk = new ContainerClosePacket(); //be sure, big crafting is really closed // pk.windowId = ContainerIds.NONE; // this.dataPacket(pk); } this.craftingType = CRAFTING_SMALL; } } public void removeAllWindows() { removeAllWindows(false); } public void removeAllWindows(boolean permanent) { for (Entry entry : new ArrayList<>(this.windowIndex.entrySet())) { if (!permanent && this.permanentWindows.contains(entry.getKey())) { continue; } this.removeWindow(entry.getValue()); } } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { this.server.getPlayerMetadata().setMetadata(this, metadataKey, newMetadataValue); } @Override public List getMetadata(String metadataKey) { return this.server.getPlayerMetadata().getMetadata(this, metadataKey); } @Override public boolean hasMetadata(String metadataKey) { return this.server.getPlayerMetadata().hasMetadata(this, metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { this.server.getPlayerMetadata().removeMetadata(this, metadataKey, owningPlugin); } @Override public void onChunkChanged(FullChunk chunk) { this.usedChunks.remove(Level.chunkHash(chunk.getX(), chunk.getZ())); } @Override public void onChunkLoaded(FullChunk chunk) { } @Override public void onChunkPopulated(FullChunk chunk) { } @Override public void onChunkUnloaded(FullChunk chunk) { } @Override public void onBlockChanged(Vector3 block) { } @Override public int getLoaderId() { return this.loaderId; } @Override public boolean isLoaderActive() { return this.isConnected(); } public static BatchPacket getChunkCacheFromData(int chunkX, int chunkZ, int subChunkCount, byte[] payload) { LevelChunkPacket pk = new LevelChunkPacket(); pk.chunkX = chunkX; pk.chunkZ = chunkZ; pk.subChunkCount = subChunkCount; pk.data = payload; pk.encode(); BatchPacket batch = new BatchPacket(); byte[][] batchPayload = new byte[2][]; byte[] buf = pk.getBuffer(); batchPayload[0] = Binary.writeUnsignedVarInt(buf.length); batchPayload[1] = buf; byte[] data = Binary.appendBytes(batchPayload); try { batch.payload = Network.deflateRaw(data, Server.getInstance().networkCompressionLevel); } catch (Exception e) { throw new RuntimeException(e); } return batch; } private boolean foodEnabled = true; public boolean isFoodEnabled() { return !(this.isCreative() || this.isSpectator()) && this.foodEnabled; } public void setFoodEnabled(boolean foodEnabled) { this.foodEnabled = foodEnabled; } public PlayerFood getFoodData() { return this.foodData; } //todo a lot on dimension private void setDimension(int dimension) { ChangeDimensionPacket pk = new ChangeDimensionPacket(); pk.dimension = dimension; pk.x = (float) this.x; pk.y = (float) this.y; pk.z = (float) this.z; this.dataPacket(pk); } @Override public boolean switchLevel(Level level) { Level oldLevel = this.level; if (super.switchLevel(level)) { SetSpawnPositionPacket spawnPosition = new SetSpawnPositionPacket(); spawnPosition.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; Position spawn = level.getSpawnLocation(); spawnPosition.x = spawn.getFloorX(); spawnPosition.y = spawn.getFloorY(); spawnPosition.z = spawn.getFloorZ(); spawnPosition.dimension = spawn.getLevel().getDimension(); this.dataPacket(spawnPosition); // Remove old chunks for (long index : new ArrayList<>(this.usedChunks.keySet())) { int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); this.unloadChunk(chunkX, chunkZ, oldLevel); } this.usedChunks.clear(); SetTimePacket setTime = new SetTimePacket(); setTime.time = level.getTime(); this.dataPacket(setTime); GameRulesChangedPacket gameRulesChanged = new GameRulesChangedPacket(); gameRulesChanged.gameRules = level.getGameRules(); this.dataPacket(gameRulesChanged); if (oldLevel.getDimension() != level.getDimension()) { this.setDimension(level.getDimension()); } updateTrackingPositions(true); return true; } return false; } public void setCheckMovement(boolean checkMovement) { this.checkMovement = checkMovement; } /** * @since 1.2.1.0-PN */ public boolean isCheckingMovement() { return this.checkMovement; } public synchronized void setLocale(Locale locale) { this.locale.set(locale); } public synchronized Locale getLocale() { return this.locale.get(); } @Override public void setSprinting(boolean value) { if (isSprinting() != value) { super.setSprinting(value); } } public void transfer(InetSocketAddress address) { String hostName = address.getAddress().getHostAddress(); int port = address.getPort(); TransferPacket pk = new TransferPacket(); pk.address = hostName; pk.port = port; this.dataPacket(pk); } public LoginChainData getLoginChainData() { return this.loginChainData; } public boolean pickupEntity(Entity entity, boolean near) { if (!this.spawned || !this.isAlive() || !this.isOnline() || this.getGamemode() == SPECTATOR || entity.isClosed()) { return false; } if (near) { Inventory inventory = this.inventory; if (entity instanceof EntityArrow && ((EntityArrow) entity).hadCollision) { ItemArrow item = new ItemArrow(); if (this.isSurvival()) { // Should only collect to the offhand slot if the item matches what is already there if (this.offhandInventory.getItem(0).getId() == item.getId() && this.offhandInventory.canAddItem(item)) { inventory = this.offhandInventory; } else if (!inventory.canAddItem(item)) { return false; } } InventoryPickupArrowEvent ev = new InventoryPickupArrowEvent(inventory, (EntityArrow) entity); int pickupMode = ((EntityArrow) entity).getPickupMode(); if (pickupMode == EntityArrow.PICKUP_NONE || pickupMode == EntityArrow.PICKUP_CREATIVE && !this.isCreative()) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } TakeItemEntityPacket pk = new TakeItemEntityPacket(); pk.entityId = this.getId(); pk.target = entity.getId(); Server.broadcastPacket(entity.getViewers().values(), pk); this.dataPacket(pk); if (!this.isCreative()) { inventory.addItem(item.clone()); } entity.close(); return true; } else if (entity instanceof EntityThrownTrident && ((EntityThrownTrident) entity).hadCollision) { Item item = ((EntityThrownTrident) entity).getItem(); if (this.isSurvival() && !inventory.canAddItem(item)) { return false; } InventoryPickupTridentEvent ev = new InventoryPickupTridentEvent(this.inventory, (EntityThrownTrident) entity); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } TakeItemEntityPacket pk = new TakeItemEntityPacket(); pk.entityId = this.getId(); pk.target = entity.getId(); Server.broadcastPacket(entity.getViewers().values(), pk); this.dataPacket(pk); if (!this.isCreative()) { inventory.addItem(item.clone()); } entity.close(); return true; } else if (entity instanceof EntityItem) { if (((EntityItem) entity).getPickupDelay() <= 0) { Item item = ((EntityItem) entity).getItem(); if (item != null) { if (this.isSurvival() && !inventory.canAddItem(item)) { return false; } InventoryPickupItemEvent ev; this.server.getPluginManager().callEvent(ev = new InventoryPickupItemEvent(inventory, (EntityItem) entity)); if (ev.isCancelled()) { return false; } switch (item.getId()) { case Item.WOOD: case Item.WOOD2: this.awardAchievement("mineWood"); break; case Item.DIAMOND: this.awardAchievement("diamond"); break; } TakeItemEntityPacket pk = new TakeItemEntityPacket(); pk.entityId = this.getId(); pk.target = entity.getId(); Server.broadcastPacket(entity.getViewers().values(), pk); this.dataPacket(pk); entity.close(); inventory.addItem(item.clone()); return true; } } } } int tick = this.getServer().getTick(); if (pickedXPOrb < tick && entity instanceof EntityXPOrb && this.boundingBox.isVectorInside(entity)) { EntityXPOrb xpOrb = (EntityXPOrb) entity; if (xpOrb.getPickupDelay() <= 0) { int exp = xpOrb.getExp(); entity.kill(); this.getLevel().addLevelEvent(LevelEventPacket.EVENT_SOUND_EXPERIENCE_ORB, 0, this); pickedXPOrb = tick; //Mending ArrayList itemsWithMending = new ArrayList<>(); for (int i = 0; i < 4; i++) { if (inventory.getArmorItem(i).getEnchantment((short)Enchantment.ID_MENDING) != null) { itemsWithMending.add(inventory.getSize() + i); } } if (inventory.getItemInHand().getEnchantment((short)Enchantment.ID_MENDING) != null) { itemsWithMending.add(inventory.getHeldItemIndex()); } if (itemsWithMending.size() > 0) { Random rand = new Random(); Integer itemToRepair = itemsWithMending.get(rand.nextInt(itemsWithMending.size())); Item toRepair = inventory.getItem(itemToRepair); if (toRepair instanceof ItemTool || toRepair instanceof ItemArmor) { if (toRepair.getDamage() > 0) { int dmg = toRepair.getDamage() - 2; if (dmg < 0) dmg = 0; toRepair.setDamage(dmg); inventory.setItem(itemToRepair, toRepair); return true; } } } this.addExperience(exp, true); return true; } } return false; } @Override public int hashCode() { if ((this.hash == 0) || (this.hash == 485)) { this.hash = (485 + (getUniqueId() != null ? getUniqueId().hashCode() : 0)); } return this.hash; } @Override public boolean equals(Object obj) { if (!(obj instanceof Player)) { return false; } Player other = (Player) obj; return Objects.equals(this.getUniqueId(), other.getUniqueId()) && this.getId() == other.getId(); } public boolean isBreakingBlock() { return this.breakingBlock != null; } /** * Show a window of a XBOX account's profile * @param xuid XUID */ public void showXboxProfile(String xuid) { ShowProfilePacket pk = new ShowProfilePacket(); pk.xuid = xuid; this.dataPacket(pk); } public void startFishing(Item fishingRod) { CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") .add(new DoubleTag("", x)) .add(new DoubleTag("", y + this.getEyeHeight())) .add(new DoubleTag("", z))) .putList(new ListTag("Motion") .add(new DoubleTag("", -Math.sin(yaw / 180 + Math.PI) * Math.cos(pitch / 180 * Math.PI))) .add(new DoubleTag("", -Math.sin(pitch / 180 * Math.PI))) .add(new DoubleTag("", Math.cos(yaw / 180 * Math.PI) * Math.cos(pitch / 180 * Math.PI)))) .putList(new ListTag("Rotation") .add(new FloatTag("", (float) yaw)) .add(new FloatTag("", (float) pitch))); double f = 1; EntityFishingHook fishingHook = new EntityFishingHook(chunk, nbt, this); fishingHook.setMotion(new Vector3(-Math.sin(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * f * f, -Math.sin(Math.toRadians(pitch)) * f * f, Math.cos(Math.toRadians(yaw)) * Math.cos(Math.toRadians(pitch)) * f * f)); ProjectileLaunchEvent ev = new ProjectileLaunchEvent(fishingHook); this.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { fishingHook.kill(); } else { fishingHook.spawnToAll(); this.fishing = fishingHook; fishingHook.rod = fishingRod; } } public void stopFishing(boolean click) { if (click) { fishing.reelLine(); } else if (this.fishing != null) { this.fishing.kill(); this.fishing.close(); } this.fishing = null; } @Override public boolean doesTriggerPressurePlate() { return this.gamemode != SPECTATOR; } private void updateBlockingFlag() { boolean shouldBlock = getNoShieldTicks() == 0 && (this.isSneaking() || getRiding() != null) && (this.getInventory().getItemInHand().getId() == ItemID.SHIELD || this.getOffhandInventory().getItem(0).getId() == ItemID.SHIELD); if (isBlocking() != shouldBlock) { this.setBlocking(shouldBlock); } } @Override protected void onBlock(Entity entity, boolean animate) { super.onBlock(entity, animate); if (animate) { this.setDataFlag(DATA_FLAGS, DATA_FLAG_BLOCKED_USING_DAMAGED_SHIELD, true); this.getServer().getScheduler().scheduleTask(null, ()-> { if (this.isOnline()) { this.setDataFlag(DATA_FLAGS, DATA_FLAG_BLOCKED_USING_DAMAGED_SHIELD, false); } }); } } @PowerNukkitOnly @Since("1.4.0.0-PN") public int getNoShieldTicks() { return noShieldTicks; } @PowerNukkitOnly @Since("1.4.0.0-PN") public void setNoShieldTicks(int noShieldTicks) { this.noShieldTicks = noShieldTicks; } @Override public String toString() { return "Player(name='" + getName() + "', location=" + super.toString() + ')'; } /** * Adds the items to the main player inventory and drops on the floor any excess. * @param items The items to give to the player. */ @PowerNukkitOnly @Since("1.4.0.0-PN") public void giveItem(Item... items) { for(Item failed: getInventory().addItem(items)) { getLevel().dropItem(this, failed); } } @PowerNukkitOnly @Since("1.4.0.0-PN") public int getTimeSinceRest() { return timeSinceRest; } @PowerNukkitOnly @Since("1.4.0.0-PN") public void setTimeSinceRest(int timeSinceRest) { this.timeSinceRest = timeSinceRest; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy