| package net.glowstone.entity; | |
| import com.flowpowered.networking.Message; | |
| import io.netty.buffer.ByteBuf; | |
| import io.netty.buffer.Unpooled; | |
| import net.glowstone.*; | |
| import net.glowstone.block.GlowBlockState; | |
| import net.glowstone.constants.GlowEffect; | |
| import net.glowstone.constants.GlowSound; | |
| import net.glowstone.entity.meta.MetadataIndex; | |
| import net.glowstone.inventory.InventoryMonitor; | |
| import net.glowstone.msg.PlayNoteMessage; | |
| import net.glowstone.msg.RespawnMessage; | |
| import net.glowstone.msg.StatisticMessage; | |
| import net.glowstone.msg.UserListItemMessage; | |
| import net.glowstone.net.GlowSession; | |
| import net.glowstone.net.message.login.LoginSuccessMessage; | |
| import net.glowstone.net.message.play.entity.DestroyEntitiesMessage; | |
| import net.glowstone.net.message.play.game.*; | |
| import net.glowstone.net.message.play.inv.*; | |
| import net.glowstone.net.protocol.PlayProtocol; | |
| import net.glowstone.util.TextWrapper; | |
| import org.bukkit.*; | |
| import org.bukkit.configuration.serialization.DelegateDeserialization; | |
| import org.bukkit.conversations.Conversation; | |
| import org.bukkit.conversations.ConversationAbandonedEvent; | |
| import org.bukkit.entity.EntityType; | |
| import org.bukkit.entity.Player; | |
| import org.bukkit.event.player.PlayerChatEvent; | |
| import org.bukkit.event.player.PlayerCommandPreprocessEvent; | |
| import org.bukkit.event.player.PlayerTeleportEvent; | |
| import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; | |
| import org.bukkit.inventory.InventoryView; | |
| import org.bukkit.inventory.ItemStack; | |
| import org.bukkit.inventory.PlayerInventory; | |
| import org.bukkit.map.MapView; | |
| import org.bukkit.plugin.Plugin; | |
| import org.bukkit.plugin.messaging.StandardMessenger; | |
| import org.bukkit.scoreboard.Scoreboard; | |
| import java.net.InetSocketAddress; | |
| import java.nio.charset.StandardCharsets; | |
| import java.util.*; | |
| import java.util.logging.Level; | |
| /** | |
| * Represents an in-game player. | |
| * @author Graham Edgecombe | |
| */ | |
| @DelegateDeserialization(GlowOfflinePlayer.class) | |
| public final class GlowPlayer extends GlowHumanEntity implements Player { | |
| /** | |
| * The normal height of a player's eyes above their feet. | |
| */ | |
| public static final double EYE_HEIGHT = 1.62D; | |
| /** | |
| * This player's session. | |
| */ | |
| private final GlowSession session; | |
| /** | |
| * This player's unique id. | |
| */ | |
| private final UUID uuid; | |
| /** | |
| * Cumulative amount of experience points the player has collected. | |
| */ | |
| private int experience = 0; | |
| /** | |
| * The current level (or skill point amount) of the player. | |
| */ | |
| private int level = 0; | |
| /** | |
| * The player's current exhaustion level. | |
| */ | |
| private float exhaustion = 0; | |
| /** | |
| * The player's current saturation level. | |
| */ | |
| private float saturation = 0; | |
| /** | |
| * This player's current time offset. | |
| */ | |
| private long timeOffset = 0; | |
| /** | |
| * Whether the time offset is relative. | |
| */ | |
| private boolean timeRelative = true; | |
| /** | |
| * The display name of this player, for chat purposes. | |
| */ | |
| private String displayName; | |
| /** | |
| * The player's compass target. | |
| */ | |
| private Location compassTarget; | |
| /** | |
| * The entities that the client knows about. | |
| */ | |
| private final Set<GlowEntity> knownEntities = new HashSet<>(); | |
| /** | |
| * The chunks that the client knows about. | |
| */ | |
| private final Set<GlowChunk.Key> knownChunks = new HashSet<>(); | |
| /** | |
| * A queue of BlockChangeMessages to be sent. | |
| */ | |
| private final List<BlockChangeMessage> blockChanges = new LinkedList<>(); | |
| /** | |
| * The set of plugin channels this player is listening on | |
| */ | |
| private final Set<String> listeningChannels = new HashSet<>(); | |
| /** | |
| * The lock used to prevent chunks from unloading near the player. | |
| */ | |
| private ChunkManager.ChunkLock chunkLock; | |
| /** | |
| * The tracker for changes to the currently open inventory. | |
| */ | |
| private InventoryMonitor invMonitor; | |
| /** | |
| * Whether the player is sneaking. | |
| */ | |
| private boolean sneaking = false; | |
| /** | |
| * The human entity's current food level | |
| */ | |
| private int food = 20; | |
| /** | |
| * The bed spawn location of a player | |
| */ | |
| private Location bedSpawn; | |
| /** | |
| * The name a player has in the player list | |
| */ | |
| private String playerListName; | |
| /** | |
| * Creates a new player and adds it to the world. | |
| * @param session The player's session. | |
| * @param name The player's name. | |
| */ | |
| public GlowPlayer(GlowSession session, String name, UUID uuid) { | |
| super(session.getServer(), (GlowWorld) session.getServer().getWorlds().get(0), name); | |
| this.session = session; | |
| this.uuid = uuid; | |
| chunkLock = world.newChunkLock(getName()); | |
| // send login response | |
| session.send(new LoginSuccessMessage(uuid.toString().replace("-", ""), name)); | |
| session.setProtocol(new PlayProtocol(session.getServer())); | |
| // send join game | |
| // in future, handle hardcore, difficulty, and level type | |
| String type = "default";//world.getWorldType().getName().toLowerCase(); | |
| int gameMode = getGameMode().getValue(); | |
| if (server.isHardcore()) { | |
| gameMode |= 0x8; | |
| } | |
| session.send(new JoinGameMessage(getEntityId(), gameMode, world.getEnvironment().getId(), world.getDifficulty().getValue(), session.getServer().getMaxPlayers(), type)); | |
| // send server brand and supported plugin channels | |
| session.send(new PluginMessage("MC|Brand", server.getName().getBytes(StandardCharsets.UTF_8))); | |
| sendSupportedChannels(); | |
| loadData(); | |
| saveData(); | |
| streamBlocks(); // stream the initial set of blocks | |
| setCompassTarget(world.getSpawnLocation()); // set our compass target | |
| session.send(new StateChangeMessage(getWorld().hasStorm() ? 2 : 1, 0)); // send the world's weather | |
| invMonitor = new InventoryMonitor(getOpenInventory()); | |
| updateInventory(); // send inventory contents | |
| // send initial location | |
| double y = location.getY() + getEyeHeight() + 0.05; | |
| session.send(new PositionRotationMessage(location.getX(), y, location.getZ(), location.getYaw(), location.getPitch(), true)); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Internals | |
| /** | |
| * Destroys this entity by removing it from the world and marking it as not | |
| * being active. | |
| */ | |
| @Override | |
| public void remove() { | |
| knownChunks.clear(); | |
| chunkLock.clear(); | |
| saveData(); | |
| getInventory().removeViewer(this); | |
| getInventory().getCraftingInventory().removeViewer(this); | |
| permissions.clearPermissions(); | |
| super.remove(); | |
| } | |
| @Override | |
| public void pulse() { | |
| super.pulse(); | |
| // stream world | |
| streamBlocks(); | |
| processBlockChanges(); | |
| // update inventory | |
| for (InventoryMonitor.Entry entry : invMonitor.getChanges()) { | |
| sendItemChange(entry.slot, entry.item); | |
| } | |
| // update or remove entities | |
| for (Iterator<GlowEntity> it = knownEntities.iterator(); it.hasNext(); ) { | |
| GlowEntity entity = it.next(); | |
| boolean withinDistance = !entity.isDead() && isWithinDistance(entity); | |
| if (withinDistance) { | |
| for (Message msg : entity.createUpdateMessage()) { | |
| session.send(msg); | |
| } | |
| } else { | |
| session.send(new DestroyEntitiesMessage(entity.getEntityId())); | |
| it.remove(); | |
| } | |
| } | |
| // add entities | |
| for (GlowEntity entity : world.getEntityManager()) { | |
| if (entity == this) | |
| continue; | |
| boolean withinDistance = !entity.isDead() && isWithinDistance(entity); | |
| if (withinDistance && !knownEntities.contains(entity)) { | |
| knownEntities.add(entity); | |
| for (Message msg : entity.createSpawnMessage()) { | |
| session.send(msg); | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Process and send pending BlockChangeMessages. | |
| */ | |
| private void processBlockChanges() { | |
| List<BlockChangeMessage> messages = new ArrayList<>(blockChanges); | |
| blockChanges.clear(); | |
| // separate messages by chunk | |
| Map<GlowChunk.Key, List<BlockChangeMessage>> chunks = new HashMap<>(); | |
| for (BlockChangeMessage message : messages) { | |
| GlowChunk.Key key = new GlowChunk.Key(message.getX() >> 4, message.getZ() >> 4); | |
| List<BlockChangeMessage> list = chunks.get(key); | |
| if (list == null) { | |
| list = new LinkedList<>(); | |
| chunks.put(key, list); | |
| } | |
| list.add(message); | |
| } | |
| // send away | |
| for (Map.Entry<GlowChunk.Key, List<BlockChangeMessage>> entry : chunks.entrySet()) { | |
| GlowChunk.Key key = entry.getKey(); | |
| List<BlockChangeMessage> value = entry.getValue(); | |
| if (value.size() == 1) { | |
| session.send(value.get(0)); | |
| } else if (value.size() > 1) { | |
| BlockChangeMessage[] records = value.toArray(new BlockChangeMessage[value.size()]); | |
| session.send(new MultiBlockChangeMessage(key.getX(), key.getZ(), records)); | |
| } | |
| } | |
| } | |
| /** | |
| * Streams chunks to the player's client. | |
| */ | |
| private void streamBlocks() { | |
| Set<GlowChunk.Key> previousChunks = new HashSet<GlowChunk.Key>(knownChunks); | |
| ArrayList<GlowChunk.Key> newChunks = new ArrayList<GlowChunk.Key>(); | |
| int centralX = location.getBlockX() >> 4; | |
| int centralZ = location.getBlockZ() >> 4; | |
| int radius = server.getViewDistance(); | |
| for (int x = (centralX - radius); x <= (centralX + radius); x++) { | |
| for (int z = (centralZ - radius); z <= (centralZ + radius); z++) { | |
| GlowChunk.Key key = new GlowChunk.Key(x, z); | |
| if (knownChunks.contains(key)) { | |
| previousChunks.remove(key); | |
| } else { | |
| newChunks.add(key); | |
| } | |
| } | |
| } | |
| if (newChunks.size() == 0 && previousChunks.size() == 0) { | |
| return; | |
| } | |
| Collections.sort(newChunks, new Comparator<GlowChunk.Key>() { | |
| public int compare(GlowChunk.Key a, GlowChunk.Key b) { | |
| double dx = 16 * a.getX() + 8 - location.getX(); | |
| double dz = 16 * a.getZ() + 8 - location.getZ(); | |
| double da = dx * dx + dz * dz; | |
| dx = 16 * b.getX() + 8 - location.getX(); | |
| dz = 16 * b.getZ() + 8 - location.getZ(); | |
| double db = dx * dx + dz * dz; | |
| return Double.compare(da, db); | |
| } | |
| }); | |
| List<GlowChunk> bulkChunks = null; | |
| if (newChunks.size() > knownChunks.size() * 2 / 5) { | |
| // send a bulk message | |
| bulkChunks = new LinkedList<GlowChunk>(); | |
| } | |
| // populate then send chunks to the player | |
| // done in two steps so that all the new chunks are finalized before any of them are sent | |
| // this prevents sending a chunk then immediately sending block changes in it because | |
| // one of its neighbors has populated | |
| for (GlowChunk.Key key : newChunks) { | |
| world.getChunkManager().forcePopulation(key.getX(), key.getZ()); | |
| } | |
| for (GlowChunk.Key key : newChunks) { | |
| GlowChunk chunk = world.getChunkAt(key.getX(), key.getZ()); | |
| if (bulkChunks == null) { | |
| session.send(chunk.toMessage()); | |
| } else { | |
| bulkChunks.add(chunk); | |
| } | |
| knownChunks.add(key); | |
| chunkLock.acquire(key); | |
| } | |
| if (bulkChunks != null) { | |
| boolean skylight = world.getEnvironment() == World.Environment.NORMAL; | |
| session.send(new ChunkBulkMessage(skylight, bulkChunks)); | |
| } | |
| for (GlowChunk.Key key : newChunks) { | |
| GlowChunk chunk = world.getChunkAt(key.getX(), key.getZ()); | |
| for (GlowBlockState state : chunk.getTileEntities()) { | |
| state.update(this); | |
| } | |
| } | |
| for (GlowChunk.Key key : previousChunks) { | |
| session.send(ChunkDataMessage.empty(key.getX(), key.getZ())); | |
| knownChunks.remove(key); | |
| chunkLock.release(key); | |
| } | |
| previousChunks.clear(); | |
| } | |
| /** | |
| * Checks whether the player can see the given chunk. | |
| * @return If the chunk is known to the player's client. | |
| */ | |
| public boolean canSee(GlowChunk.Key chunk) { | |
| return knownChunks.contains(chunk); | |
| } | |
| /** | |
| * Checks whether the player can see the given entity. | |
| * @return If the entity is known to the player's client. | |
| */ | |
| public boolean canSee(GlowEntity entity) { | |
| return knownEntities.contains(entity); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Basic getters | |
| /** | |
| * Gets the session. | |
| * @return The session. | |
| */ | |
| public GlowSession getSession() { | |
| return session; | |
| } | |
| public boolean isOnline() { | |
| return true; | |
| } | |
| @Override | |
| public UUID getUniqueId() { | |
| return uuid; | |
| } | |
| public boolean isBanned() { | |
| return server.getBanList(BanList.Type.NAME).isBanned(getName()); | |
| } | |
| @Deprecated | |
| public void setBanned(boolean banned) { | |
| server.getBanList(BanList.Type.NAME).addBan(getName(), null, null, null); | |
| } | |
| public boolean isWhitelisted() { | |
| return !server.hasWhitelist() || server.getWhitelist().contains(getName()); | |
| } | |
| public void setWhitelisted(boolean value) { | |
| if (value) { | |
| server.getWhitelist().add(getName()); | |
| } else { | |
| server.getWhitelist().remove(getName()); | |
| } | |
| } | |
| public Player getPlayer() { | |
| return this; | |
| } | |
| public InetSocketAddress getAddress() { | |
| return session.getAddress(); | |
| } | |
| @Override | |
| public boolean isOp() { | |
| return getServer().getOpsList().contains(getName()); | |
| } | |
| @Override | |
| public void setOp(boolean value) { | |
| if (value) { | |
| getServer().getOpsList().add(getName()); | |
| } else { | |
| getServer().getOpsList().remove(getName()); | |
| } | |
| permissions.recalculatePermissions(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Editable properties | |
| public String getDisplayName() { | |
| return displayName == null ? getName() : displayName; | |
| } | |
| public void setDisplayName(String name) { | |
| displayName = name; | |
| } | |
| public String getPlayerListName() { | |
| return playerListName == null || "".equals(playerListName) ? getName() : playerListName; | |
| } | |
| public void setPlayerListName(String name) { | |
| if (name.length() > 15) | |
| throw new IllegalArgumentException("The given name was " + name.length() + " chars long, longer than the maximum of 16"); | |
| for (Player player : server.getOnlinePlayers()) { | |
| if (player.getPlayerListName().equals(getPlayerListName())) | |
| throw new IllegalArgumentException("The name given, " + name + ", is already used by " + player.getName() + "."); | |
| } | |
| net.glowstone.msg.Message removeMessage = new UserListItemMessage(getPlayerListName(), false, (short) 0); | |
| playerListName = name; | |
| net.glowstone.msg.Message reAddMessage = new UserListItemMessage(getPlayerListName(), true, (short) 0); | |
| for (Player player : server.getOnlinePlayers()) { | |
| ((GlowPlayer) player).getSession().send(removeMessage); | |
| ((GlowPlayer) player).getSession().send(reAddMessage); | |
| } | |
| } | |
| public Location getCompassTarget() { | |
| return compassTarget; | |
| } | |
| public void setCompassTarget(Location loc) { | |
| compassTarget = loc; | |
| session.send(new SpawnPositionMessage(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); | |
| } | |
| public boolean isSneaking() { | |
| return (metadata.getByte(MetadataIndex.STATUS) & 0x02) != 0; | |
| } | |
| public void setSneaking(boolean sneak) { | |
| if (EventFactory.onPlayerToggleSneak(this, sneak).isCancelled()) { | |
| return; | |
| } | |
| if (sneak) { | |
| metadata.setBit(MetadataIndex.STATUS, 0x02); | |
| } else { | |
| metadata.clearBit(MetadataIndex.STATUS, 0x02); | |
| } | |
| updateMetadata(); | |
| } | |
| public boolean isSprinting() { | |
| return metadata.getBit(MetadataIndex.STATUS, 0x08); | |
| } | |
| public void setSprinting(boolean sprinting) { | |
| // todo: event | |
| if (sprinting) { | |
| metadata.setBit(MetadataIndex.STATUS, 0x08); | |
| } else { | |
| metadata.clearBit(MetadataIndex.STATUS, 0x08); | |
| } | |
| updateMetadata(); | |
| } | |
| public boolean isSleepingIgnored() { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| public void setSleepingIgnored(boolean isSleeping) { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| @Override | |
| public void setGameMode(GameMode mode) { | |
| boolean changed = getGameMode() != mode; | |
| super.setGameMode(mode); | |
| if (changed) session.send(new StateChangeMessage(3, mode.getValue())); | |
| } | |
| // todo: most of the exp stuff is pretty broken | |
| public int getExperience() { | |
| return experience % ((getLevel() + 1) * 7); | |
| } | |
| public void setExperience(int exp) { | |
| setTotalExperience(experience - getExperience() + exp); | |
| } | |
| public int getLevel() { | |
| return level; | |
| } | |
| public void setLevel(int level) { | |
| experience = 0; | |
| for (this.level = 0; this.level < level; ++this.level) { | |
| experience += getExpToLevel(); | |
| } | |
| session.send(createExperienceMessage()); | |
| } | |
| public int getTotalExperience() { | |
| return experience; | |
| } | |
| public void setTotalExperience(int exp) { | |
| int calcExperience = exp; | |
| this.experience = exp; | |
| level = 0; | |
| while ((calcExperience -= getExpToLevel()) > 0) ++level; | |
| session.send(createExperienceMessage()); | |
| } | |
| public void giveExp(int xp) { | |
| experience += xp; | |
| while (experience > (getLevel() + 1) * 7) { | |
| experience -= (getLevel() + 1) * 7; | |
| ++level; | |
| } | |
| session.send(createExperienceMessage()); | |
| } | |
| public float getExp() { | |
| return (float) experience / getExpToLevel(); | |
| } | |
| public void setExp(float percentToLevel) { | |
| experience = (int) (percentToLevel * getExpToLevel()); | |
| } | |
| @Override | |
| public int getExpToLevel() { | |
| return getExpToLevel(level); | |
| } | |
| private int getExpToLevel(int level) { | |
| if (level >= 30) { | |
| return 62 + (level - 30) * 7; | |
| } else if (level >= 15) { | |
| return 17 + (level - 15) * 3; | |
| } else { | |
| return 17; | |
| } | |
| } | |
| public float getExhaustion() { | |
| return exhaustion; | |
| } | |
| public void setExhaustion(float value) { | |
| exhaustion = value; | |
| } | |
| public float getSaturation() { | |
| return saturation; | |
| } | |
| public void setSaturation(float value) { | |
| saturation = value; | |
| session.send(createHealthMessage()); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Actions | |
| /** | |
| * Teleport the player. | |
| * @param location The destination to teleport to. | |
| * @return Whether the teleport was a success. | |
| */ | |
| @Override | |
| public boolean teleport(Location location) { | |
| return teleport(location, TeleportCause.UNKNOWN); | |
| } | |
| @Override | |
| public boolean teleport(Location location, TeleportCause cause) { | |
| if (this.location != null && this.location.getWorld() != null) { | |
| PlayerTeleportEvent event = EventFactory.onPlayerTeleport(this, getLocation(), location, cause); | |
| if (event.isCancelled()) return false; | |
| location = event.getTo(); | |
| } | |
| // account for floating point shenanigans in client physics | |
| double y = location.getY() + getEyeHeight() + 0.05; | |
| PositionRotationMessage message = new PositionRotationMessage(location.getX(), y, location.getZ(), location.getYaw(), location.getPitch(), true); | |
| if (location.getWorld() != world) { | |
| GlowWorld oldWorld = world; | |
| world.getEntityManager().deallocate(this); | |
| world = (GlowWorld) location.getWorld(); | |
| world.getEntityManager().allocate(this); | |
| for (GlowChunk.Key key : knownChunks) { | |
| session.send(ChunkDataMessage.empty(key.getX(), key.getZ())); | |
| } | |
| knownChunks.clear(); | |
| chunkLock.clear(); | |
| chunkLock = world.newChunkLock(getName()); | |
| session.send(new RespawnMessage((byte) world.getEnvironment().getId(), (byte) 1, (byte) getGameMode().getValue(), (short) world.getMaxHeight(), world.getSeed())); | |
| streamBlocks(); // stream blocks | |
| setCompassTarget(world.getSpawnLocation()); // set our compass target | |
| this.session.send(message); | |
| this.location = location; // take us to spawn position | |
| session.send(new StateChangeMessage((byte) (getWorld().hasStorm() ? 1 : 2), (byte) 0)); // send the world's weather | |
| reset(); | |
| EventFactory.onPlayerChangedWorld(this, oldWorld); | |
| } else { | |
| this.session.send(message); | |
| this.location = location; | |
| reset(); | |
| } | |
| return true; | |
| } | |
| public void sendMessage(String message) { | |
| sendRawMessage(message); | |
| } | |
| public void sendMessage(String[] messages) { | |
| for (String line : messages) { | |
| sendMessage(line); | |
| } | |
| } | |
| public void sendRawMessage(String message) { | |
| // todo: use chat components instead of plain text | |
| // textwrapper also does not preserve non-color formatting | |
| for (String line : TextWrapper.wrapText(message)) { | |
| session.send(new ChatMessage(line)); | |
| } | |
| } | |
| public void kickPlayer(String message) { | |
| session.disconnect(message == null ? "" : message); | |
| } | |
| public boolean performCommand(String command) { | |
| return getServer().dispatchCommand(this, command); | |
| } | |
| /** | |
| * Says a message (or runs a command). | |
| * @param text message to print | |
| */ | |
| public void chat(String text) { | |
| if (text.startsWith("/")) { | |
| try { | |
| PlayerCommandPreprocessEvent event = EventFactory.onPlayerCommand(this, text); | |
| if (event.isCancelled()) { | |
| return; | |
| } | |
| server.getLogger().info(event.getPlayer().getName() + " issued command: " + event.getMessage()); | |
| getServer().dispatchCommand(event.getPlayer(), event.getMessage().substring(1)); | |
| } catch (Exception ex) { | |
| sendMessage(ChatColor.RED + "An internal error occured while executing your command."); | |
| getServer().getLogger().log(Level.SEVERE, "Exception while executing command: " + text, ex); | |
| } | |
| } else { | |
| PlayerChatEvent event = EventFactory.onPlayerChat(this, text); | |
| if (event.isCancelled()) { | |
| return; | |
| } | |
| String message = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); | |
| getServer().getLogger().info(message); | |
| for (Player recipient : event.getRecipients()) { | |
| recipient.sendMessage(message); | |
| } | |
| } | |
| } | |
| public void saveData() { | |
| saveData(true); | |
| } | |
| public void saveData(boolean async) { | |
| final GlowWorld dataWorld = (GlowWorld) server.getWorlds().get(0); | |
| if (async) { | |
| server.getScheduler().runTaskAsynchronously(null, new Runnable() { | |
| public void run() { | |
| dataWorld.getMetadataService().writePlayerData(GlowPlayer.this); | |
| } | |
| }); | |
| } else { | |
| dataWorld.getMetadataService().writePlayerData(this); | |
| } | |
| } | |
| public void loadData() { | |
| GlowWorld dataWorld = (GlowWorld) server.getWorlds().get(0); | |
| dataWorld.getMetadataService().readPlayerData(this); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Effect and data transmission | |
| public void playNote(Location loc, Instrument instrument, Note note) { | |
| playNote(loc, instrument.getType(), note.getId()); | |
| } | |
| public void playNote(Location loc, byte instrument, byte note) { | |
| session.send(new PlayNoteMessage(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), instrument, note)); | |
| } | |
| public void playEffect(Location loc, Effect effect, int data) { | |
| int id = effect.getId(); | |
| boolean ignoreDistance = id == 1013; // mob.wither.spawn, not in Bukkit yet | |
| session.send(new PlayEffectMessage(id, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), data, ignoreDistance)); | |
| } | |
| public <T> void playEffect(Location loc, Effect effect, T data) { | |
| playEffect(loc, effect, GlowEffect.getDataValue(effect, data)); | |
| } | |
| public void playSound(Location location, Sound sound, float volume, float pitch) { | |
| playSound(location, GlowSound.getName(sound), volume, pitch); | |
| } | |
| public void playSound(Location location, String sound, float volume, float pitch) { | |
| session.send(new PlaySoundMessage(sound, location.getBlockX(), location.getBlockY(), location.getBlockZ(), volume, pitch)); | |
| } | |
| public void sendBlockChange(Location loc, Material material, byte data) { | |
| sendBlockChange(loc, material.getId(), data); | |
| } | |
| public void sendBlockChange(Location loc, int material, byte data) { | |
| sendBlockChange(new BlockChangeMessage(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), material, data)); | |
| } | |
| public void sendBlockChange(BlockChangeMessage message) { | |
| // only send message if the chunk is within visible range | |
| GlowChunk.Key key = new GlowChunk.Key(message.getX() >> 4, message.getZ() >> 4); | |
| if (canSee(key)) { | |
| blockChanges.add(message); | |
| } | |
| } | |
| public boolean sendChunkChange(Location loc, int sx, int sy, int sz, byte[] data) { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| public void sendMap(MapView map) { | |
| throw new UnsupportedOperationException("Not supported yet."); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Achievements and statistics | |
| public void awardAchievement(Achievement achievement) { | |
| //sendStatistic(achievement.getId(), 1); | |
| } | |
| public void incrementStatistic(Statistic statistic) { | |
| incrementStatistic(statistic, 1); | |
| } | |
| public void incrementStatistic(Statistic statistic, int amount) { | |
| //sendStatistic(statistic.getId(), amount); | |
| } | |
| public void incrementStatistic(Statistic statistic, Material material) { | |
| incrementStatistic(statistic, material, 1); | |
| } | |
| public void incrementStatistic(Statistic statistic, Material material, int amount) { | |
| if (!statistic.isSubstatistic()) { | |
| throw new IllegalArgumentException("Given statistic is not a substatistic"); | |
| } | |
| if (statistic.isBlock() != material.isBlock()) { | |
| throw new IllegalArgumentException("Given material is not valid for this substatistic"); | |
| } | |
| int mat = material.getId(); | |
| if (!material.isBlock()) { | |
| mat -= 255; | |
| } | |
| //sendStatistic(statistic.getId() + mat, amount); | |
| } | |
| private void sendStatistic(int id, int amount) { | |
| while (amount > Byte.MAX_VALUE) { | |
| sendStatistic(id, Byte.MAX_VALUE); | |
| amount -= Byte.MAX_VALUE; | |
| } | |
| if (amount > 0) { | |
| session.send(new StatisticMessage(id, (byte) amount)); | |
| } | |
| } | |
| public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { | |
| } | |
| public void removeAchievement(Achievement achievement) { | |
| } | |
| public boolean hasAchievement(Achievement achievement) { | |
| return false; | |
| } | |
| public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { | |
| } | |
| public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { | |
| } | |
| public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException { | |
| } | |
| public int getStatistic(Statistic statistic) throws IllegalArgumentException { | |
| return 0; | |
| } | |
| public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { | |
| } | |
| public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException { | |
| return 0; | |
| } | |
| public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { | |
| } | |
| public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException { | |
| } | |
| public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { | |
| } | |
| public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { | |
| } | |
| public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { | |
| return 0; | |
| } | |
| public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) throws IllegalArgumentException { | |
| } | |
| public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Inventory | |
| public void updateInventory() { | |
| session.send(new SetWindowContentsMessage(invMonitor.getId(), invMonitor.getContents())); | |
| } | |
| public void sendItemChange(int slot, ItemStack item) { | |
| session.send(new SetWindowSlotMessage(invMonitor.getId(), slot, item)); | |
| } | |
| @Override | |
| public void setItemOnCursor(ItemStack item) { | |
| super.setItemOnCursor(item); | |
| session.send(new SetWindowSlotMessage(-1, -1, item)); | |
| } | |
| @Override | |
| public boolean setWindowProperty(InventoryView.Property prop, int value) { | |
| if (!super.setWindowProperty(prop, value)) return false; | |
| session.send(new WindowPropertyMessage(invMonitor.getId(), prop.getId(), value)); | |
| return true; | |
| } | |
| @Override | |
| public void openInventory(InventoryView view) { | |
| session.send(new CloseWindowMessage(invMonitor.getId())); | |
| super.openInventory(view); | |
| invMonitor = new InventoryMonitor(getOpenInventory()); | |
| int viewId = invMonitor.getId(); | |
| if (viewId != 0) { | |
| String title = view.getTitle(); | |
| boolean useTitle = !view.getType().getDefaultTitle().equals(title); | |
| if (view.getTopInventory() instanceof PlayerInventory && !useTitle) { | |
| title = ((PlayerInventory) view.getTopInventory()).getHolder().getName(); | |
| useTitle = true; | |
| } | |
| Message open = new OpenWindowMessage(viewId, invMonitor.getType(), title, view.getTopInventory().getSize(), useTitle); | |
| session.send(open); | |
| } | |
| updateInventory(); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Player time | |
| public void setPlayerTime(long time, boolean relative) { | |
| timeOffset = time % 24000; | |
| timeRelative = relative; | |
| if (timeOffset < 0) timeOffset += 24000; | |
| } | |
| public long getPlayerTime() { | |
| if (timeRelative) { | |
| // add timeOffset ticks to current time | |
| return (world.getTime() + timeOffset) % 24000; | |
| } else { | |
| // return time offset | |
| return timeOffset % 24000; | |
| } | |
| } | |
| public long getPlayerTimeOffset() { | |
| return timeOffset; | |
| } | |
| public boolean isPlayerTimeRelative() { | |
| return timeRelative; | |
| } | |
| public void resetPlayerTime() { | |
| setPlayerTime(0, true); | |
| } | |
| @Override | |
| public void setHealth(double health) { | |
| super.setHealth(health); | |
| session.send(createHealthMessage()); | |
| } | |
| public int getFoodLevel() { | |
| return food; | |
| } | |
| public void setFoodLevel(int food) { | |
| this.food = Math.min(food, 20); | |
| session.send(createHealthMessage()); | |
| } | |
| public HealthMessage createHealthMessage() { | |
| return new HealthMessage((float) getHealth(), getFoodLevel(), getSaturation()); | |
| } | |
| public ExperienceMessage createExperienceMessage() { | |
| return new ExperienceMessage(getExp(), (byte) getLevel(), (short) getTotalExperience()); | |
| } | |
| public Map<String, Object> serialize() { | |
| Map<String, Object> ret = new HashMap<String, Object>(); | |
| ret.put("name", getName()); | |
| return ret; | |
| } | |
| // NEW STUFF | |
| @Override | |
| public void setPlayerWeather(WeatherType type) { | |
| } | |
| @Override | |
| public WeatherType getPlayerWeather() { | |
| return null; | |
| } | |
| @Override | |
| public void resetPlayerWeather() { | |
| } | |
| @Override | |
| public void giveExpLevels(int amount) { | |
| } | |
| @Override | |
| public void setBedSpawnLocation(Location location, boolean force) { | |
| } | |
| @Override | |
| public boolean getAllowFlight() { | |
| return false; | |
| } | |
| @Override | |
| public void setAllowFlight(boolean flight) { | |
| } | |
| @Override | |
| public void hidePlayer(Player player) { | |
| } | |
| @Override | |
| public void showPlayer(Player player) { | |
| } | |
| @Override | |
| public boolean canSee(Player player) { | |
| return false; | |
| } | |
| @Override | |
| public boolean isFlying() { | |
| return false; | |
| } | |
| @Override | |
| public void setFlying(boolean value) { | |
| } | |
| @Override | |
| public void setFlySpeed(float value) throws IllegalArgumentException { | |
| } | |
| @Override | |
| public void setWalkSpeed(float value) throws IllegalArgumentException { | |
| } | |
| @Override | |
| public float getFlySpeed() { | |
| return 0; | |
| } | |
| @Override | |
| public float getWalkSpeed() { | |
| return 0; | |
| } | |
| @Override | |
| public void setTexturePack(String url) { | |
| } | |
| @Override | |
| public void setResourcePack(String url) { | |
| } | |
| @Override | |
| public Scoreboard getScoreboard() { | |
| return null; | |
| } | |
| @Override | |
| public void setScoreboard(Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException { | |
| } | |
| @Override | |
| public boolean isHealthScaled() { | |
| return false; | |
| } | |
| @Override | |
| public void setHealthScaled(boolean scale) { | |
| } | |
| @Override | |
| public void setHealthScale(double scale) throws IllegalArgumentException { | |
| } | |
| @Override | |
| public double getHealthScale() { | |
| return 0; | |
| } | |
| @Override | |
| public boolean isConversing() { | |
| return false; | |
| } | |
| @Override | |
| public void acceptConversationInput(String input) { | |
| } | |
| @Override | |
| public boolean beginConversation(Conversation conversation) { | |
| return false; | |
| } | |
| @Override | |
| public void abandonConversation(Conversation conversation) { | |
| } | |
| @Override | |
| public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { | |
| } | |
| @Override | |
| public long getFirstPlayed() { | |
| return 0; | |
| } | |
| @Override | |
| public long getLastPlayed() { | |
| return 0; | |
| } | |
| @Override | |
| public boolean hasPlayedBefore() { | |
| return false; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Plugin messages | |
| @Override | |
| public void sendPluginMessage(Plugin source, String channel, byte[] message) { | |
| StandardMessenger.validatePluginMessage(getServer().getMessenger(), source, channel, message); | |
| if (listeningChannels.contains(channel)) { | |
| // only send if player is listening for it | |
| session.send(new PluginMessage(channel, message)); | |
| } | |
| } | |
| @Override | |
| public Set<String> getListeningPluginChannels() { | |
| return Collections.unmodifiableSet(listeningChannels); | |
| } | |
| /** | |
| * Add a listening channel to this player. | |
| * @param channel The channel to add. | |
| */ | |
| public void addChannel(String channel) { | |
| if (listeningChannels.add(channel)) { | |
| // EventFactory.callEvent(new PlayerRegisterChannelEvent(this, channel)); | |
| } | |
| } | |
| /** | |
| * Remove a listening channel from this player. | |
| * @param channel The channel to remove. | |
| */ | |
| public void removeChannel(String channel) { | |
| if (listeningChannels.remove(channel)) { | |
| // EventFactory.callEvent(new PlayerUnregisterChannelEvent(this, channel)); | |
| } | |
| } | |
| /** | |
| * Send the supported plugin channels to the client. | |
| */ | |
| private void sendSupportedChannels() { | |
| Set<String> listening = server.getMessenger().getIncomingChannels(); | |
| if (!listening.isEmpty()) { | |
| // send NUL-separated list of channels we support | |
| ByteBuf buf = Unpooled.buffer(16 * listening.size()); | |
| for (String channel : listening) { | |
| buf.writeBytes(channel.getBytes(StandardCharsets.UTF_8)); | |
| buf.writeByte(0); | |
| } | |
| session.send(new PluginMessage("REGISTER", buf.array())); | |
| } | |
| } | |
| } |