2076 lines
77 KiB
Java
2076 lines
77 KiB
Java
package org.bukkit.craftbukkit.entity;
|
|
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.io.BaseEncoding;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.datafixers.util.Pair;
|
|
import io.netty.buffer.Unpooled;
|
|
import it.unimi.dsi.fastutil.shorts.ShortArraySet;
|
|
import it.unimi.dsi.fastutil.shorts.ShortSet;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.lang.ref.WeakReference;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.SocketAddress;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.WeakHashMap;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import javax.annotation.Nullable;
|
|
import net.minecraft.advancements.AdvancementProgress;
|
|
import net.minecraft.core.BlockPosition;
|
|
import net.minecraft.core.Holder;
|
|
import net.minecraft.core.SectionPosition;
|
|
import net.minecraft.core.registries.BuiltInRegistries;
|
|
import net.minecraft.nbt.NBTTagCompound;
|
|
import net.minecraft.network.PacketDataSerializer;
|
|
import net.minecraft.network.chat.IChatBaseComponent;
|
|
import net.minecraft.network.chat.PlayerChatMessage;
|
|
import net.minecraft.network.protocol.Packet;
|
|
import net.minecraft.network.protocol.game.ClientboundClearTitlesPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundHurtAnimationPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetSubtitleTextPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
|
|
import net.minecraft.network.protocol.game.ClientboundSetTitlesAnimationPacket;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutBlockBreakAnimation;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutBlockChange;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutCustomPayload;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutEntitySound;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutExperience;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutMultiBlockChange;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutNamedSoundEffect;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutSpawnPosition;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutStopSound;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutTileEntityData;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutUpdateHealth;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutWorldEvent;
|
|
import net.minecraft.network.protocol.game.PacketPlayOutWorldParticles;
|
|
import net.minecraft.resources.MinecraftKey;
|
|
import net.minecraft.server.AdvancementDataPlayer;
|
|
import net.minecraft.server.level.EntityPlayer;
|
|
import net.minecraft.server.level.PlayerChunkMap;
|
|
import net.minecraft.server.level.WorldServer;
|
|
import net.minecraft.server.network.PlayerConnection;
|
|
import net.minecraft.server.players.WhiteListEntry;
|
|
import net.minecraft.sounds.SoundEffect;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.entity.EntityLiving;
|
|
import net.minecraft.world.entity.EnumItemSlot;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeMapBase;
|
|
import net.minecraft.world.entity.ai.attributes.AttributeModifiable;
|
|
import net.minecraft.world.entity.ai.attributes.GenericAttributes;
|
|
import net.minecraft.world.entity.player.EntityHuman;
|
|
import net.minecraft.world.inventory.Container;
|
|
import net.minecraft.world.item.EnumColor;
|
|
import net.minecraft.world.level.EnumGamemode;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.entity.SignText;
|
|
import net.minecraft.world.level.block.entity.TileEntity;
|
|
import net.minecraft.world.level.block.entity.TileEntitySign;
|
|
import net.minecraft.world.level.block.state.IBlockData;
|
|
import net.minecraft.world.level.border.IWorldBorderListener;
|
|
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
|
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
|
import net.minecraft.world.phys.Vec3D;
|
|
import org.bukkit.BanList;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.DyeColor;
|
|
import org.bukkit.Effect;
|
|
import org.bukkit.GameMode;
|
|
import org.bukkit.Instrument;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.NamespacedKey;
|
|
import org.bukkit.Note;
|
|
import org.bukkit.OfflinePlayer;
|
|
import org.bukkit.Particle;
|
|
import org.bukkit.Sound;
|
|
import org.bukkit.Statistic;
|
|
import org.bukkit.WeatherType;
|
|
import org.bukkit.WorldBorder;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockState;
|
|
import org.bukkit.block.Sign;
|
|
import org.bukkit.block.TileState;
|
|
import org.bukkit.block.data.BlockData;
|
|
import org.bukkit.block.sign.Side;
|
|
import org.bukkit.configuration.serialization.DelegateDeserialization;
|
|
import org.bukkit.conversations.Conversation;
|
|
import org.bukkit.conversations.ConversationAbandonedEvent;
|
|
import org.bukkit.conversations.ManuallyAbandonedConversationCanceller;
|
|
import org.bukkit.craftbukkit.CraftEffect;
|
|
import org.bukkit.craftbukkit.CraftEquipmentSlot;
|
|
import org.bukkit.craftbukkit.CraftOfflinePlayer;
|
|
import org.bukkit.craftbukkit.CraftParticle;
|
|
import org.bukkit.craftbukkit.CraftServer;
|
|
import org.bukkit.craftbukkit.CraftSound;
|
|
import org.bukkit.craftbukkit.CraftStatistic;
|
|
import org.bukkit.craftbukkit.CraftWorld;
|
|
import org.bukkit.craftbukkit.CraftWorldBorder;
|
|
import org.bukkit.craftbukkit.advancement.CraftAdvancement;
|
|
import org.bukkit.craftbukkit.advancement.CraftAdvancementProgress;
|
|
import org.bukkit.craftbukkit.block.CraftBlockEntityState;
|
|
import org.bukkit.craftbukkit.block.CraftBlockState;
|
|
import org.bukkit.craftbukkit.block.CraftBlockStates;
|
|
import org.bukkit.craftbukkit.block.CraftSign;
|
|
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
import org.bukkit.craftbukkit.conversations.ConversationTracker;
|
|
import org.bukkit.craftbukkit.event.CraftEventFactory;
|
|
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
|
import org.bukkit.craftbukkit.map.CraftMapView;
|
|
import org.bukkit.craftbukkit.map.RenderData;
|
|
import org.bukkit.craftbukkit.profile.CraftPlayerProfile;
|
|
import org.bukkit.craftbukkit.scoreboard.CraftScoreboard;
|
|
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
|
import org.bukkit.craftbukkit.util.CraftLocation;
|
|
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
|
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
|
import org.bukkit.entity.EntityType;
|
|
import org.bukkit.entity.LivingEntity;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.player.PlayerExpCooldownChangeEvent;
|
|
import org.bukkit.event.player.PlayerHideEntityEvent;
|
|
import org.bukkit.event.player.PlayerRegisterChannelEvent;
|
|
import org.bukkit.event.player.PlayerShowEntityEvent;
|
|
import org.bukkit.event.player.PlayerSpawnChangeEvent;
|
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
|
import org.bukkit.event.player.PlayerUnregisterChannelEvent;
|
|
import org.bukkit.inventory.EquipmentSlot;
|
|
import org.bukkit.inventory.InventoryView.Property;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.map.MapCursor;
|
|
import org.bukkit.map.MapView;
|
|
import org.bukkit.metadata.MetadataValue;
|
|
import org.bukkit.plugin.Plugin;
|
|
import org.bukkit.plugin.messaging.StandardMessenger;
|
|
import org.bukkit.profile.PlayerProfile;
|
|
import org.bukkit.scoreboard.Scoreboard;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
@DelegateDeserialization(CraftOfflinePlayer.class)
|
|
public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
private long firstPlayed = 0;
|
|
private long lastPlayed = 0;
|
|
private boolean hasPlayedBefore = false;
|
|
private final ConversationTracker conversationTracker = new ConversationTracker();
|
|
private final Set<String> channels = new HashSet<String>();
|
|
private final Map<UUID, Set<WeakReference<Plugin>>> invertedVisibilityEntities = new HashMap<>();
|
|
private static final WeakHashMap<Plugin, WeakReference<Plugin>> pluginWeakReferences = new WeakHashMap<>();
|
|
private int hash = 0;
|
|
private double health = 20;
|
|
private boolean scaledHealth = false;
|
|
private double healthScale = 20;
|
|
private CraftWorldBorder clientWorldBorder = null;
|
|
private IWorldBorderListener clientWorldBorderListener = createWorldBorderListener();
|
|
|
|
public CraftPlayer(CraftServer server, EntityPlayer entity) {
|
|
super(server, entity);
|
|
|
|
firstPlayed = System.currentTimeMillis();
|
|
}
|
|
|
|
public GameProfile getProfile() {
|
|
return getHandle().getGameProfile();
|
|
}
|
|
|
|
@Override
|
|
public boolean isOp() {
|
|
return server.getHandle().isOp(getProfile());
|
|
}
|
|
|
|
@Override
|
|
public void setOp(boolean value) {
|
|
if (value == isOp()) return;
|
|
|
|
if (value) {
|
|
server.getHandle().op(getProfile());
|
|
} else {
|
|
server.getHandle().deop(getProfile());
|
|
}
|
|
|
|
perm.recalculatePermissions();
|
|
}
|
|
|
|
@Override
|
|
public boolean isOnline() {
|
|
return server.getPlayer(getUniqueId()) != null;
|
|
}
|
|
|
|
@Override
|
|
public PlayerProfile getPlayerProfile() {
|
|
return new CraftPlayerProfile(getProfile());
|
|
}
|
|
|
|
@Override
|
|
public InetSocketAddress getAddress() {
|
|
if (getHandle().connection == null) return null;
|
|
|
|
SocketAddress addr = getHandle().connection.getRemoteAddress();
|
|
if (addr instanceof InetSocketAddress) {
|
|
return (InetSocketAddress) addr;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public double getEyeHeight(boolean ignorePose) {
|
|
if (ignorePose) {
|
|
return 1.62D;
|
|
} else {
|
|
return getEyeHeight();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendRawMessage(String message) {
|
|
this.sendRawMessage(null, message);
|
|
}
|
|
|
|
@Override
|
|
public void sendRawMessage(UUID sender, String message) {
|
|
Preconditions.checkArgument(message != null, "message cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
for (IChatBaseComponent component : CraftChatMessage.fromString(message)) {
|
|
getHandle().sendSystemMessage(component);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(String message) {
|
|
if (!conversationTracker.isConversingModaly()) {
|
|
this.sendRawMessage(message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(String... messages) {
|
|
for (String message : messages) {
|
|
sendMessage(message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(UUID sender, String message) {
|
|
if (!conversationTracker.isConversingModaly()) {
|
|
this.sendRawMessage(sender, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(UUID sender, String... messages) {
|
|
for (String message : messages) {
|
|
sendMessage(sender, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getDisplayName() {
|
|
return getHandle().displayName;
|
|
}
|
|
|
|
@Override
|
|
public void setDisplayName(final String name) {
|
|
getHandle().displayName = name == null ? getName() : name;
|
|
}
|
|
|
|
@Override
|
|
public String getPlayerListName() {
|
|
return getHandle().listName == null ? getName() : CraftChatMessage.fromComponent(getHandle().listName);
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerListName(String name) {
|
|
if (name == null) {
|
|
name = getName();
|
|
}
|
|
getHandle().listName = name.equals(getName()) ? null : CraftChatMessage.fromStringOrNull(name);
|
|
for (EntityPlayer player : (List<EntityPlayer>) server.getHandle().players) {
|
|
if (player.getBukkitEntity().canSee(this)) {
|
|
player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.a.UPDATE_DISPLAY_NAME, getHandle()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private IChatBaseComponent playerListHeader;
|
|
private IChatBaseComponent playerListFooter;
|
|
|
|
@Override
|
|
public String getPlayerListHeader() {
|
|
return (playerListHeader == null) ? null : CraftChatMessage.fromComponent(playerListHeader);
|
|
}
|
|
|
|
@Override
|
|
public String getPlayerListFooter() {
|
|
return (playerListFooter == null) ? null : CraftChatMessage.fromComponent(playerListFooter);
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerListHeader(String header) {
|
|
this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true);
|
|
updatePlayerListHeaderFooter();
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerListFooter(String footer) {
|
|
this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true);
|
|
updatePlayerListHeaderFooter();
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerListHeaderFooter(String header, String footer) {
|
|
this.playerListHeader = CraftChatMessage.fromStringOrNull(header, true);
|
|
this.playerListFooter = CraftChatMessage.fromStringOrNull(footer, true);
|
|
updatePlayerListHeaderFooter();
|
|
}
|
|
|
|
private void updatePlayerListHeaderFooter() {
|
|
if (getHandle().connection == null) return;
|
|
|
|
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter((this.playerListHeader == null) ? IChatBaseComponent.empty() : this.playerListHeader, (this.playerListFooter == null) ? IChatBaseComponent.empty() : this.playerListFooter);
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (!(obj instanceof OfflinePlayer)) {
|
|
return false;
|
|
}
|
|
OfflinePlayer other = (OfflinePlayer) obj;
|
|
if ((this.getUniqueId() == null) || (other.getUniqueId() == null)) {
|
|
return false;
|
|
}
|
|
|
|
boolean uuidEquals = this.getUniqueId().equals(other.getUniqueId());
|
|
boolean idEquals = true;
|
|
|
|
if (other instanceof CraftPlayer) {
|
|
idEquals = this.getEntityId() == ((CraftPlayer) other).getEntityId();
|
|
}
|
|
|
|
return uuidEquals && idEquals;
|
|
}
|
|
|
|
@Override
|
|
public void kickPlayer(String message) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.disconnect(message == null ? "" : message);
|
|
}
|
|
|
|
@Override
|
|
public void setCompassTarget(Location loc) {
|
|
Preconditions.checkArgument(loc != null, "Location cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
// Do not directly assign here, from the packethandler we'll assign it.
|
|
getHandle().connection.send(new PacketPlayOutSpawnPosition(CraftLocation.toBlockPosition(loc), loc.getYaw()));
|
|
}
|
|
|
|
@Override
|
|
public Location getCompassTarget() {
|
|
return getHandle().compassTarget;
|
|
}
|
|
|
|
@Override
|
|
public void chat(String msg) {
|
|
Preconditions.checkArgument(msg != null, "msg cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.chat(msg, PlayerChatMessage.system(msg), false);
|
|
}
|
|
|
|
@Override
|
|
public boolean performCommand(String command) {
|
|
Preconditions.checkArgument(command != null, "command cannot be null");
|
|
return server.dispatchCommand(this, command);
|
|
}
|
|
|
|
@Override
|
|
public void playNote(Location loc, byte instrument, byte note) {
|
|
playNote(loc, Instrument.getByType(instrument), new Note(note));
|
|
}
|
|
|
|
@Override
|
|
public void playNote(Location loc, Instrument instrument, Note note) {
|
|
Preconditions.checkArgument(loc != null, "Location cannot be null");
|
|
Preconditions.checkArgument(instrument != null, "Instrument cannot be null");
|
|
Preconditions.checkArgument(note != null, "Note cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
String instrumentName = switch (instrument.ordinal()) {
|
|
case 0 -> "harp";
|
|
case 1 -> "basedrum";
|
|
case 2 -> "snare";
|
|
case 3 -> "hat";
|
|
case 4 -> "bass";
|
|
case 5 -> "flute";
|
|
case 6 -> "bell";
|
|
case 7 -> "guitar";
|
|
case 8 -> "chime";
|
|
case 9 -> "xylophone";
|
|
case 10 -> "iron_xylophone";
|
|
case 11 -> "cow_bell";
|
|
case 12 -> "didgeridoo";
|
|
case 13 -> "bit";
|
|
case 14 -> "banjo";
|
|
case 15 -> "pling";
|
|
case 16 -> "xylophone";
|
|
default -> null;
|
|
};
|
|
|
|
float f = (float) Math.pow(2.0D, (note.getId() - 12.0D) / 12.0D);
|
|
getHandle().connection.send(new PacketPlayOutNamedSoundEffect(BuiltInRegistries.SOUND_EVENT.wrapAsHolder(CraftSound.getSoundEffect("block.note_block." + instrumentName)), net.minecraft.sounds.SoundCategory.RECORDS, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ(), 3.0f, f, getHandle().getRandom().nextLong()));
|
|
}
|
|
|
|
@Override
|
|
public void playSound(Location loc, Sound sound, float volume, float pitch) {
|
|
playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(Location loc, String sound, float volume, float pitch) {
|
|
playSound(loc, sound, org.bukkit.SoundCategory.MASTER, volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch) {
|
|
if (loc == null || sound == null || category == null || getHandle().connection == null) return;
|
|
|
|
playSound0(loc, BuiltInRegistries.SOUND_EVENT.wrapAsHolder(CraftSound.getSoundEffect(sound)), net.minecraft.sounds.SoundCategory.valueOf(category.name()), volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch) {
|
|
if (loc == null || sound == null || category == null || getHandle().connection == null) return;
|
|
|
|
playSound0(loc, Holder.direct(SoundEffect.createVariableRangeEvent(new MinecraftKey(sound))), net.minecraft.sounds.SoundCategory.valueOf(category.name()), volume, pitch);
|
|
}
|
|
|
|
private void playSound0(Location loc, Holder<SoundEffect> soundEffectHolder, net.minecraft.sounds.SoundCategory categoryNMS, float volume, float pitch) {
|
|
Preconditions.checkArgument(loc != null, "Location cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
PacketPlayOutNamedSoundEffect packet = new PacketPlayOutNamedSoundEffect(soundEffectHolder, categoryNMS, loc.getX(), loc.getY(), loc.getZ(), volume, pitch, getHandle().getRandom().nextLong());
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(org.bukkit.entity.Entity entity, Sound sound, float volume, float pitch) {
|
|
playSound(entity, sound, org.bukkit.SoundCategory.MASTER, volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(org.bukkit.entity.Entity entity, String sound, float volume, float pitch) {
|
|
playSound(entity, sound, org.bukkit.SoundCategory.MASTER, volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(org.bukkit.entity.Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch) {
|
|
if (!(entity instanceof CraftEntity craftEntity) || sound == null || category == null || getHandle().connection == null) return;
|
|
|
|
playSound0(entity, BuiltInRegistries.SOUND_EVENT.wrapAsHolder(CraftSound.getSoundEffect(sound)), net.minecraft.sounds.SoundCategory.valueOf(category.name()), volume, pitch);
|
|
}
|
|
|
|
@Override
|
|
public void playSound(org.bukkit.entity.Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch) {
|
|
if (!(entity instanceof CraftEntity craftEntity) || sound == null || category == null || getHandle().connection == null) return;
|
|
|
|
playSound0(entity, Holder.direct(SoundEffect.createVariableRangeEvent(new MinecraftKey(sound))), net.minecraft.sounds.SoundCategory.valueOf(category.name()), volume, pitch);
|
|
}
|
|
|
|
private void playSound0(org.bukkit.entity.Entity entity, Holder<SoundEffect> soundEffectHolder, net.minecraft.sounds.SoundCategory categoryNMS, float volume, float pitch) {
|
|
Preconditions.checkArgument(entity != null, "Entity cannot be null");
|
|
Preconditions.checkArgument(soundEffectHolder != null, "Holder of SoundEffect cannot be null");
|
|
Preconditions.checkArgument(categoryNMS != null, "SoundCategory cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
if (!(entity instanceof CraftEntity craftEntity)) return;
|
|
|
|
PacketPlayOutEntitySound packet = new PacketPlayOutEntitySound(soundEffectHolder, categoryNMS, craftEntity.getHandle(), volume, pitch, getHandle().getRandom().nextLong());
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void stopSound(Sound sound) {
|
|
stopSound(sound, null);
|
|
}
|
|
|
|
@Override
|
|
public void stopSound(String sound) {
|
|
stopSound(sound, null);
|
|
}
|
|
|
|
@Override
|
|
public void stopSound(Sound sound, org.bukkit.SoundCategory category) {
|
|
stopSound(sound.getKey().getKey(), category);
|
|
}
|
|
|
|
@Override
|
|
public void stopSound(String sound, org.bukkit.SoundCategory category) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.send(new PacketPlayOutStopSound(new MinecraftKey(sound), category == null ? net.minecraft.sounds.SoundCategory.MASTER : net.minecraft.sounds.SoundCategory.valueOf(category.name())));
|
|
}
|
|
|
|
@Override
|
|
public void stopSound(org.bukkit.SoundCategory category) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.send(new PacketPlayOutStopSound(null, net.minecraft.sounds.SoundCategory.valueOf(category.name())));
|
|
}
|
|
|
|
@Override
|
|
public void stopAllSounds() {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.send(new PacketPlayOutStopSound(null, null));
|
|
}
|
|
|
|
@Override
|
|
public void playEffect(Location loc, Effect effect, int data) {
|
|
Preconditions.checkArgument(effect != null, "Effect cannot be null");
|
|
Preconditions.checkArgument(loc != null, "Location cannot be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
int packetData = effect.getId();
|
|
PacketPlayOutWorldEvent packet = new PacketPlayOutWorldEvent(packetData, CraftLocation.toBlockPosition(loc), data, false);
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public <T> void playEffect(Location loc, Effect effect, T data) {
|
|
Preconditions.checkArgument(effect != null, "Effect cannot be null");
|
|
if (data != null) {
|
|
Preconditions.checkArgument(effect.getData() != null, "Effect.%s does not have a valid Data", effect);
|
|
Preconditions.checkArgument(effect.getData().isAssignableFrom(data.getClass()), "%s data cannot be used for the %s effect", data.getClass().getName(), effect);
|
|
} else {
|
|
// Special case: the axis is optional for ELECTRIC_SPARK
|
|
Preconditions.checkArgument(effect.getData() == null || effect == Effect.ELECTRIC_SPARK, "Wrong kind of data for the %s effect", effect);
|
|
}
|
|
|
|
int datavalue = CraftEffect.getDataValue(effect, data);
|
|
playEffect(loc, effect, datavalue);
|
|
}
|
|
|
|
@Override
|
|
public boolean breakBlock(Block block) {
|
|
Preconditions.checkArgument(block != null, "Block cannot be null");
|
|
Preconditions.checkArgument(block.getWorld().equals(getWorld()), "Cannot break blocks across worlds");
|
|
|
|
return getHandle().gameMode.destroyBlock(new BlockPosition(block.getX(), block.getY(), block.getZ()));
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockChange(Location loc, Material material, byte data) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(CraftLocation.toBlockPosition(loc), CraftMagicNumbers.getBlock(material, data));
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockChange(Location loc, BlockData block) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
PacketPlayOutBlockChange packet = new PacketPlayOutBlockChange(CraftLocation.toBlockPosition(loc), ((CraftBlockData) block).getState());
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockChanges(Collection<BlockState> blocks) {
|
|
Preconditions.checkArgument(blocks != null, "blocks must not be null");
|
|
|
|
if (getHandle().connection == null || blocks.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
Map<SectionPosition, ChunkSectionChanges> changes = new HashMap<>();
|
|
for (BlockState state : blocks) {
|
|
CraftBlockState cstate = (CraftBlockState) state;
|
|
BlockPosition blockPosition = cstate.getPosition();
|
|
|
|
// The coordinates of the chunk section in which the block is located, aka chunk x, y, and z
|
|
SectionPosition sectionPosition = SectionPosition.of(blockPosition);
|
|
|
|
// Push the block change position and block data to the final change map
|
|
ChunkSectionChanges sectionChanges = changes.computeIfAbsent(sectionPosition, (ignore) -> new ChunkSectionChanges());
|
|
|
|
sectionChanges.positions().add(SectionPosition.sectionRelativePos(blockPosition));
|
|
sectionChanges.blockData().add(cstate.getHandle());
|
|
}
|
|
|
|
// Construct the packets using the data allocated above and send then to the players
|
|
for (Map.Entry<SectionPosition, ChunkSectionChanges> entry : changes.entrySet()) {
|
|
ChunkSectionChanges chunkChanges = entry.getValue();
|
|
PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(entry.getKey(), chunkChanges.positions(), chunkChanges.blockData().toArray(IBlockData[]::new));
|
|
getHandle().connection.send(packet);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockChanges(Collection<BlockState> blocks, boolean suppressLightUpdates) {
|
|
this.sendBlockChanges(blocks);
|
|
}
|
|
|
|
private record ChunkSectionChanges(ShortSet positions, List<IBlockData> blockData) {
|
|
|
|
public ChunkSectionChanges() {
|
|
this(new ShortArraySet(), new ArrayList<>());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockDamage(Location loc, float progress) {
|
|
this.sendBlockDamage(loc, progress, getEntityId());
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockDamage(Location loc, float progress, org.bukkit.entity.Entity source) {
|
|
Preconditions.checkArgument(source != null, "source must not be null");
|
|
this.sendBlockDamage(loc, progress, source.getEntityId());
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockDamage(Location loc, float progress, int sourceId) {
|
|
Preconditions.checkArgument(loc != null, "loc must not be null");
|
|
Preconditions.checkArgument(progress >= 0.0 && progress <= 1.0, "progress must be between 0.0 and 1.0 (inclusive)");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
int stage = (int) (9 * progress); // There are 0 - 9 damage states
|
|
if (progress == 0.0F) {
|
|
stage = -1; // The protocol states that any other value will reset the damage, which this API promises
|
|
}
|
|
|
|
PacketPlayOutBlockBreakAnimation packet = new PacketPlayOutBlockBreakAnimation(sourceId, CraftLocation.toBlockPosition(loc), stage);
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void sendSignChange(Location loc, String[] lines) {
|
|
sendSignChange(loc, lines, DyeColor.BLACK);
|
|
}
|
|
|
|
@Override
|
|
public void sendSignChange(Location loc, String[] lines, DyeColor dyeColor) {
|
|
sendSignChange(loc, lines, dyeColor, false);
|
|
}
|
|
|
|
@Override
|
|
public void sendSignChange(Location loc, String[] lines, DyeColor dyeColor, boolean hasGlowingText) {
|
|
Preconditions.checkArgument(loc != null, "Location cannot be null");
|
|
Preconditions.checkArgument(dyeColor != null, "DyeColor cannot be null");
|
|
|
|
if (lines == null) {
|
|
lines = new String[4];
|
|
}
|
|
Preconditions.checkArgument(lines.length < 4, "lines (%s) must be lower than 4", lines.length);
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
IChatBaseComponent[] components = CraftSign.sanitizeLines(lines);
|
|
TileEntitySign sign = new TileEntitySign(CraftLocation.toBlockPosition(loc), Blocks.OAK_SIGN.defaultBlockState());
|
|
SignText text = sign.getFrontText();
|
|
text.setColor(EnumColor.byId(dyeColor.getWoolData()));
|
|
text.setHasGlowingText(hasGlowingText);
|
|
for (int i = 0; i < components.length; i++) {
|
|
text.setMessage(i, components[i]);
|
|
}
|
|
|
|
getHandle().connection.send(sign.getUpdatePacket());
|
|
}
|
|
|
|
@Override
|
|
public void sendBlockUpdate(@NotNull Location location, @NotNull TileState tileState) throws IllegalArgumentException {
|
|
Preconditions.checkArgument(location != null, "Location can not be null");
|
|
Preconditions.checkArgument(tileState != null, "TileState can not be null");
|
|
|
|
if (getHandle().connection == null) return;
|
|
|
|
CraftBlockEntityState<?> craftState = ((CraftBlockEntityState<?>) tileState);
|
|
getHandle().connection.send(craftState.getUpdatePacket(location));
|
|
}
|
|
|
|
@Override
|
|
public void sendEquipmentChange(LivingEntity entity, EquipmentSlot slot, ItemStack item) {
|
|
this.sendEquipmentChange(entity, Map.of(slot, item));
|
|
}
|
|
|
|
@Override
|
|
public void sendEquipmentChange(LivingEntity entity, Map<EquipmentSlot, ItemStack> items) {
|
|
Preconditions.checkArgument(entity != null, "Entity cannot be null");
|
|
Preconditions.checkArgument(items != null, "items cannot be null");
|
|
|
|
if (getHandle().connection == null) {
|
|
return;
|
|
}
|
|
|
|
List<Pair<EnumItemSlot, net.minecraft.world.item.ItemStack>> equipment = new ArrayList<>(items.size());
|
|
for (Map.Entry<EquipmentSlot, ItemStack> entry : items.entrySet()) {
|
|
EquipmentSlot slot = entry.getKey();
|
|
Preconditions.checkArgument(slot != null, "Cannot set null EquipmentSlot");
|
|
|
|
equipment.add(new Pair<>(CraftEquipmentSlot.getNMS(slot), CraftItemStack.asNMSCopy(entry.getValue())));
|
|
}
|
|
|
|
getHandle().connection.send(new PacketPlayOutEntityEquipment(entity.getEntityId(), equipment));
|
|
}
|
|
|
|
@Override
|
|
public WorldBorder getWorldBorder() {
|
|
return clientWorldBorder;
|
|
}
|
|
|
|
@Override
|
|
public void setWorldBorder(WorldBorder border) {
|
|
CraftWorldBorder craftBorder = (CraftWorldBorder) border;
|
|
|
|
if (border != null && !craftBorder.isVirtual() && !craftBorder.getWorld().equals(getWorld())) {
|
|
throw new UnsupportedOperationException("Cannot set player world border to that of another world");
|
|
}
|
|
|
|
// Nullify the old client-sided world border listeners so that calls to it will not affect this player
|
|
if (clientWorldBorder != null) {
|
|
this.clientWorldBorder.getHandle().removeListener(clientWorldBorderListener);
|
|
}
|
|
|
|
net.minecraft.world.level.border.WorldBorder newWorldBorder;
|
|
if (craftBorder == null || !craftBorder.isVirtual()) {
|
|
this.clientWorldBorder = null;
|
|
newWorldBorder = ((CraftWorldBorder) getWorld().getWorldBorder()).getHandle();
|
|
} else {
|
|
this.clientWorldBorder = craftBorder;
|
|
this.clientWorldBorder.getHandle().addListener(clientWorldBorderListener);
|
|
newWorldBorder = clientWorldBorder.getHandle();
|
|
}
|
|
|
|
// Send all world border update packets to the player
|
|
PlayerConnection connection = getHandle().connection;
|
|
connection.send(new ClientboundSetBorderSizePacket(newWorldBorder));
|
|
connection.send(new ClientboundSetBorderLerpSizePacket(newWorldBorder));
|
|
connection.send(new ClientboundSetBorderCenterPacket(newWorldBorder));
|
|
connection.send(new ClientboundSetBorderWarningDelayPacket(newWorldBorder));
|
|
connection.send(new ClientboundSetBorderWarningDistancePacket(newWorldBorder));
|
|
}
|
|
|
|
private IWorldBorderListener createWorldBorderListener() {
|
|
return new IWorldBorderListener() {
|
|
@Override
|
|
public void onBorderSizeSet(net.minecraft.world.level.border.WorldBorder border, double size) {
|
|
getHandle().connection.send(new ClientboundSetBorderSizePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSizeLerping(net.minecraft.world.level.border.WorldBorder border, double size, double newSize, long time) {
|
|
getHandle().connection.send(new ClientboundSetBorderLerpSizePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderCenterSet(net.minecraft.world.level.border.WorldBorder border, double x, double z) {
|
|
getHandle().connection.send(new ClientboundSetBorderCenterPacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetWarningTime(net.minecraft.world.level.border.WorldBorder border, int warningTime) {
|
|
getHandle().connection.send(new ClientboundSetBorderWarningDelayPacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetWarningBlocks(net.minecraft.world.level.border.WorldBorder border, int warningBlocks) {
|
|
getHandle().connection.send(new ClientboundSetBorderWarningDistancePacket(border));
|
|
}
|
|
|
|
@Override
|
|
public void onBorderSetDamagePerBlock(net.minecraft.world.level.border.WorldBorder border, double damage) {} // NO OP
|
|
|
|
@Override
|
|
public void onBorderSetDamageSafeZOne(net.minecraft.world.level.border.WorldBorder border, double blocks) {} // NO OP
|
|
};
|
|
}
|
|
|
|
public boolean hasClientWorldBorder() {
|
|
return clientWorldBorder != null;
|
|
}
|
|
|
|
@Override
|
|
public void sendMap(MapView map) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
RenderData data = ((CraftMapView) map).render(this);
|
|
Collection<MapIcon> icons = new ArrayList<MapIcon>();
|
|
for (MapCursor cursor : data.cursors) {
|
|
if (cursor.isVisible()) {
|
|
icons.add(new MapIcon(MapIcon.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), CraftChatMessage.fromStringOrNull(cursor.getCaption())));
|
|
}
|
|
}
|
|
|
|
PacketPlayOutMap packet = new PacketPlayOutMap(map.getId(), map.getScale().getValue(), map.isLocked(), icons, new WorldMap.b(0, 0, 128, 128, data.buffer));
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void sendHurtAnimation(float yaw) {
|
|
if (getHandle().connection == null) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Vanilla degrees state that 0 = left, 90 = front, 180 = right, and 270 = behind.
|
|
* This makes no sense. We'll add 90 to it so that 0 = front, clockwise from there.
|
|
*/
|
|
float actualYaw = yaw + 90;
|
|
getHandle().connection.send(new ClientboundHurtAnimationPacket(getEntityId(), actualYaw));
|
|
}
|
|
|
|
@Override
|
|
public void addCustomChatCompletions(Collection<String> completions) {
|
|
this.sendCustomChatCompletionPacket(completions, ClientboundCustomChatCompletionsPacket.Action.ADD);
|
|
}
|
|
|
|
@Override
|
|
public void removeCustomChatCompletions(Collection<String> completions) {
|
|
this.sendCustomChatCompletionPacket(completions, ClientboundCustomChatCompletionsPacket.Action.REMOVE);
|
|
}
|
|
|
|
@Override
|
|
public void setCustomChatCompletions(Collection<String> completions) {
|
|
this.sendCustomChatCompletionPacket(completions, ClientboundCustomChatCompletionsPacket.Action.SET);
|
|
}
|
|
|
|
private void sendCustomChatCompletionPacket(Collection<String> completions, ClientboundCustomChatCompletionsPacket.Action action) {
|
|
if (getHandle().connection == null) return;
|
|
|
|
ClientboundCustomChatCompletionsPacket packet = new ClientboundCustomChatCompletionsPacket(action, new ArrayList<>(completions));
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Override
|
|
public void setRotation(float yaw, float pitch) {
|
|
throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead.");
|
|
}
|
|
|
|
@Override
|
|
public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) {
|
|
Preconditions.checkArgument(location != null, "location");
|
|
Preconditions.checkArgument(location.getWorld() != null, "location.world");
|
|
location.checkFinite();
|
|
|
|
EntityPlayer entity = getHandle();
|
|
|
|
if (getHealth() == 0 || entity.isRemoved()) {
|
|
return false;
|
|
}
|
|
|
|
if (entity.connection == null) {
|
|
return false;
|
|
}
|
|
|
|
if (entity.isVehicle()) {
|
|
return false;
|
|
}
|
|
|
|
// From = Players current Location
|
|
Location from = this.getLocation();
|
|
// To = Players new Location if Teleport is Successful
|
|
Location to = location;
|
|
// Create & Call the Teleport Event.
|
|
PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause);
|
|
server.getPluginManager().callEvent(event);
|
|
|
|
// Return False to inform the Plugin that the Teleport was unsuccessful/cancelled.
|
|
if (event.isCancelled()) {
|
|
return false;
|
|
}
|
|
|
|
// If this player is riding another entity, we must dismount before teleporting.
|
|
entity.stopRiding();
|
|
|
|
// SPIGOT-5509: Wakeup, similar to riding
|
|
if (this.isSleeping()) {
|
|
this.wakeup(false);
|
|
}
|
|
|
|
// Update the From Location
|
|
from = event.getFrom();
|
|
// Grab the new To Location dependent on whether the event was cancelled.
|
|
to = event.getTo();
|
|
// Grab the To and From World Handles.
|
|
WorldServer fromWorld = ((CraftWorld) from.getWorld()).getHandle();
|
|
WorldServer toWorld = ((CraftWorld) to.getWorld()).getHandle();
|
|
|
|
// Close any foreign inventory
|
|
if (getHandle().containerMenu != getHandle().inventoryMenu) {
|
|
getHandle().closeContainer();
|
|
}
|
|
|
|
// Check if the fromWorld and toWorld are the same.
|
|
if (fromWorld == toWorld) {
|
|
entity.connection.teleport(to);
|
|
} else {
|
|
// The respawn reason should never be used if the passed location is non null.
|
|
server.getHandle().respawn(entity, toWorld, true, to, true, null);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void setSneaking(boolean sneak) {
|
|
getHandle().setShiftKeyDown(sneak);
|
|
}
|
|
|
|
@Override
|
|
public boolean isSneaking() {
|
|
return getHandle().isShiftKeyDown();
|
|
}
|
|
|
|
@Override
|
|
public boolean isSprinting() {
|
|
return getHandle().isSprinting();
|
|
}
|
|
|
|
@Override
|
|
public void setSprinting(boolean sprinting) {
|
|
getHandle().setSprinting(sprinting);
|
|
}
|
|
|
|
@Override
|
|
public void loadData() {
|
|
server.getHandle().playerIo.load(getHandle());
|
|
}
|
|
|
|
@Override
|
|
public void saveData() {
|
|
server.getHandle().playerIo.save(getHandle());
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
public void updateInventory() {
|
|
getHandle().containerMenu.sendAllDataToRemote();
|
|
}
|
|
|
|
@Override
|
|
public void setSleepingIgnored(boolean isSleeping) {
|
|
getHandle().fauxSleeping = isSleeping;
|
|
((CraftWorld) getWorld()).getHandle().updateSleepingPlayerList();
|
|
}
|
|
|
|
@Override
|
|
public boolean isSleepingIgnored() {
|
|
return getHandle().fauxSleeping;
|
|
}
|
|
|
|
@Override
|
|
public Location getBedSpawnLocation() {
|
|
WorldServer world = getHandle().server.getLevel(getHandle().getRespawnDimension());
|
|
BlockPosition bed = getHandle().getRespawnPosition();
|
|
|
|
if (world != null && bed != null) {
|
|
Optional<Vec3D> spawnLoc = EntityHuman.findRespawnPositionAndUseSpawnBlock(world, bed, getHandle().getRespawnAngle(), getHandle().isRespawnForced(), true);
|
|
if (spawnLoc.isPresent()) {
|
|
Vec3D vec = spawnLoc.get();
|
|
return CraftLocation.toBukkit(vec, world.getWorld(), getHandle().getRespawnAngle(), 0);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void setBedSpawnLocation(Location location) {
|
|
setBedSpawnLocation(location, false);
|
|
}
|
|
|
|
@Override
|
|
public void setBedSpawnLocation(Location location, boolean override) {
|
|
if (location == null) {
|
|
getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN);
|
|
} else {
|
|
getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Location getBedLocation() {
|
|
Preconditions.checkState(isSleeping(), "Not sleeping");
|
|
|
|
BlockPosition bed = getHandle().getRespawnPosition();
|
|
return CraftLocation.toBukkit(bed, getWorld());
|
|
}
|
|
|
|
@Override
|
|
public boolean hasDiscoveredRecipe(NamespacedKey recipe) {
|
|
Preconditions.checkArgument(recipe != null, "recipe cannot be null");
|
|
return getHandle().getRecipeBook().contains(CraftNamespacedKey.toMinecraft(recipe));
|
|
}
|
|
|
|
@Override
|
|
public Set<NamespacedKey> getDiscoveredRecipes() {
|
|
ImmutableSet.Builder<NamespacedKey> bukkitRecipeKeys = ImmutableSet.builder();
|
|
getHandle().getRecipeBook().known.forEach(key -> bukkitRecipeKeys.add(CraftNamespacedKey.fromMinecraft(key)));
|
|
return bukkitRecipeKeys.build();
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic);
|
|
}
|
|
|
|
@Override
|
|
public int getStatistic(Statistic statistic) {
|
|
return CraftStatistic.getStatistic(getHandle().getStats(), statistic);
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic, int amount) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic, amount);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic, int amount) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic, amount);
|
|
}
|
|
|
|
@Override
|
|
public void setStatistic(Statistic statistic, int newValue) {
|
|
CraftStatistic.setStatistic(getHandle().getStats(), statistic, newValue);
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic, Material material) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic, material);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic, Material material) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic, material);
|
|
}
|
|
|
|
@Override
|
|
public int getStatistic(Statistic statistic, Material material) {
|
|
return CraftStatistic.getStatistic(getHandle().getStats(), statistic, material);
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic, Material material, int amount) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic, material, amount);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic, Material material, int amount) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic, material, amount);
|
|
}
|
|
|
|
@Override
|
|
public void setStatistic(Statistic statistic, Material material, int newValue) {
|
|
CraftStatistic.setStatistic(getHandle().getStats(), statistic, material, newValue);
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic, EntityType entityType) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic, entityType);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic, EntityType entityType) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic, entityType);
|
|
}
|
|
|
|
@Override
|
|
public int getStatistic(Statistic statistic, EntityType entityType) {
|
|
return CraftStatistic.getStatistic(getHandle().getStats(), statistic, entityType);
|
|
}
|
|
|
|
@Override
|
|
public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) {
|
|
CraftStatistic.incrementStatistic(getHandle().getStats(), statistic, entityType, amount);
|
|
}
|
|
|
|
@Override
|
|
public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) {
|
|
CraftStatistic.decrementStatistic(getHandle().getStats(), statistic, entityType, amount);
|
|
}
|
|
|
|
@Override
|
|
public void setStatistic(Statistic statistic, EntityType entityType, int newValue) {
|
|
CraftStatistic.setStatistic(getHandle().getStats(), statistic, entityType, newValue);
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerTime(long time, boolean relative) {
|
|
getHandle().timeOffset = time;
|
|
getHandle().relativeTime = relative;
|
|
}
|
|
|
|
@Override
|
|
public long getPlayerTimeOffset() {
|
|
return getHandle().timeOffset;
|
|
}
|
|
|
|
@Override
|
|
public long getPlayerTime() {
|
|
return getHandle().getPlayerTime();
|
|
}
|
|
|
|
@Override
|
|
public boolean isPlayerTimeRelative() {
|
|
return getHandle().relativeTime;
|
|
}
|
|
|
|
@Override
|
|
public void resetPlayerTime() {
|
|
setPlayerTime(0, true);
|
|
}
|
|
|
|
@Override
|
|
public void setPlayerWeather(WeatherType type) {
|
|
getHandle().setPlayerWeather(type, true);
|
|
}
|
|
|
|
@Override
|
|
public WeatherType getPlayerWeather() {
|
|
return getHandle().getPlayerWeather();
|
|
}
|
|
|
|
@Override
|
|
public int getExpCooldown() {
|
|
return getHandle().takeXpDelay;
|
|
}
|
|
|
|
@Override
|
|
public void setExpCooldown(int ticks) {
|
|
getHandle().takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(this.getHandle(), ticks, PlayerExpCooldownChangeEvent.ChangeReason.PLUGIN).getNewCooldown();
|
|
}
|
|
|
|
@Override
|
|
public void resetPlayerWeather() {
|
|
getHandle().resetPlayerWeather();
|
|
}
|
|
|
|
@Override
|
|
public boolean isBanned() {
|
|
return server.getBanList(BanList.Type.NAME).isBanned(getName());
|
|
}
|
|
|
|
@Override
|
|
public boolean isWhitelisted() {
|
|
return server.getHandle().getWhiteList().isWhiteListed(getProfile());
|
|
}
|
|
|
|
@Override
|
|
public void setWhitelisted(boolean value) {
|
|
if (value) {
|
|
server.getHandle().getWhiteList().add(new WhiteListEntry(getProfile()));
|
|
} else {
|
|
server.getHandle().getWhiteList().remove(getProfile());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setGameMode(GameMode mode) {
|
|
Preconditions.checkArgument(mode != null, "GameMode cannot be null");
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().setGameMode(EnumGamemode.byId(mode.getValue()));
|
|
}
|
|
|
|
@Override
|
|
public GameMode getGameMode() {
|
|
return GameMode.getByValue(getHandle().gameMode.getGameModeForPlayer().getId());
|
|
}
|
|
|
|
@Override
|
|
public GameMode getPreviousGameMode() {
|
|
EnumGamemode previousGameMode = getHandle().gameMode.getPreviousGameModeForPlayer();
|
|
|
|
return (previousGameMode == null) ? null : GameMode.getByValue(previousGameMode.getId());
|
|
}
|
|
|
|
@Override
|
|
public void giveExp(int exp) {
|
|
getHandle().giveExperiencePoints(exp);
|
|
}
|
|
|
|
@Override
|
|
public void giveExpLevels(int levels) {
|
|
getHandle().giveExperienceLevels(levels);
|
|
}
|
|
|
|
@Override
|
|
public float getExp() {
|
|
return getHandle().experienceProgress;
|
|
}
|
|
|
|
@Override
|
|
public void setExp(float exp) {
|
|
Preconditions.checkArgument(exp >= 0.0 && exp <= 1.0, "Experience progress must be between 0.0 and 1.0 (%s)", exp);
|
|
getHandle().experienceProgress = exp;
|
|
getHandle().lastSentExp = -1;
|
|
}
|
|
|
|
@Override
|
|
public int getLevel() {
|
|
return getHandle().experienceLevel;
|
|
}
|
|
|
|
@Override
|
|
public void setLevel(int level) {
|
|
Preconditions.checkArgument(level >= 0, "Experience level must not be negative (%s)", level);
|
|
getHandle().experienceLevel = level;
|
|
getHandle().lastSentExp = -1;
|
|
}
|
|
|
|
@Override
|
|
public int getTotalExperience() {
|
|
return getHandle().totalExperience;
|
|
}
|
|
|
|
@Override
|
|
public void setTotalExperience(int exp) {
|
|
Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp);
|
|
getHandle().totalExperience = exp;
|
|
}
|
|
|
|
@Override
|
|
public void sendExperienceChange(float progress) {
|
|
sendExperienceChange(progress, getLevel());
|
|
}
|
|
|
|
@Override
|
|
public void sendExperienceChange(float progress, int level) {
|
|
Preconditions.checkArgument(progress >= 0.0 && progress <= 1.0, "Experience progress must be between 0.0 and 1.0 (%s)", progress);
|
|
Preconditions.checkArgument(level >= 0, "Experience level must not be negative (%s)", level);
|
|
|
|
if (getHandle().connection == null) {
|
|
return;
|
|
}
|
|
|
|
PacketPlayOutExperience packet = new PacketPlayOutExperience(progress, getTotalExperience(), level);
|
|
getHandle().connection.send(packet);
|
|
}
|
|
|
|
@Nullable
|
|
private static WeakReference<Plugin> getPluginWeakReference(@Nullable Plugin plugin) {
|
|
return (plugin == null) ? null : pluginWeakReferences.computeIfAbsent(plugin, WeakReference::new);
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public void hidePlayer(Player player) {
|
|
hideEntity0(null, player);
|
|
}
|
|
|
|
@Override
|
|
public void hidePlayer(Plugin plugin, Player player) {
|
|
hideEntity(plugin, player);
|
|
}
|
|
|
|
@Override
|
|
public void hideEntity(Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Preconditions.checkArgument(plugin != null, "Plugin cannot be null");
|
|
Preconditions.checkArgument(plugin.isEnabled(), "Plugin (%s) cannot be disabled", plugin.getName());
|
|
|
|
hideEntity0(plugin, entity);
|
|
}
|
|
|
|
private void hideEntity0(@Nullable Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Preconditions.checkArgument(entity != null, "Entity hidden cannot be null");
|
|
if (getHandle().connection == null) return;
|
|
if (equals(entity)) return;
|
|
|
|
boolean shouldHide;
|
|
if (entity.isVisibleByDefault()) {
|
|
shouldHide = addInvertedVisibility(plugin, entity);
|
|
} else {
|
|
shouldHide = removeInvertedVisibility(plugin, entity);
|
|
}
|
|
|
|
if (shouldHide) {
|
|
untrackAndHideEntity(entity);
|
|
}
|
|
}
|
|
|
|
private boolean addInvertedVisibility(@Nullable Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Set<WeakReference<Plugin>> invertedPlugins = invertedVisibilityEntities.get(entity.getUniqueId());
|
|
if (invertedPlugins != null) {
|
|
// Some plugins are already inverting the entity. Just mark that this
|
|
// plugin wants the entity inverted too and end.
|
|
invertedPlugins.add(getPluginWeakReference(plugin));
|
|
return false;
|
|
}
|
|
invertedPlugins = new HashSet<>();
|
|
invertedPlugins.add(getPluginWeakReference(plugin));
|
|
invertedVisibilityEntities.put(entity.getUniqueId(), invertedPlugins);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void untrackAndHideEntity(org.bukkit.entity.Entity entity) {
|
|
// Remove this entity from the hidden player's EntityTrackerEntry
|
|
PlayerChunkMap tracker = ((WorldServer) getHandle().level()).getChunkSource().chunkMap;
|
|
Entity other = ((CraftEntity) entity).getHandle();
|
|
PlayerChunkMap.EntityTracker entry = tracker.entityMap.get(other.getId());
|
|
if (entry != null) {
|
|
entry.removePlayer(getHandle());
|
|
}
|
|
|
|
// Remove the hidden entity from this player user list, if they're on it
|
|
if (other instanceof EntityPlayer) {
|
|
EntityPlayer otherPlayer = (EntityPlayer) other;
|
|
if (otherPlayer.sentListPacket) {
|
|
getHandle().connection.send(new ClientboundPlayerInfoRemovePacket(List.of(otherPlayer.getUUID())));
|
|
}
|
|
}
|
|
|
|
server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity));
|
|
}
|
|
|
|
void resetAndHideEntity(org.bukkit.entity.Entity entity) {
|
|
// SPIGOT-7312: Can't show/hide self
|
|
if (equals(entity)) {
|
|
return;
|
|
}
|
|
|
|
if (invertedVisibilityEntities.remove(entity.getUniqueId()) == null) {
|
|
untrackAndHideEntity(entity);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public void showPlayer(Player player) {
|
|
showEntity0(null, player);
|
|
}
|
|
|
|
@Override
|
|
public void showPlayer(Plugin plugin, Player player) {
|
|
showEntity(plugin, player);
|
|
}
|
|
|
|
@Override
|
|
public void showEntity(Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Preconditions.checkArgument(plugin != null, "Plugin cannot be null");
|
|
// Don't require that plugin be enabled. A plugin must be allowed to call
|
|
// showPlayer during its onDisable() method.
|
|
showEntity0(plugin, entity);
|
|
}
|
|
|
|
private void showEntity0(@Nullable Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Preconditions.checkArgument(entity != null, "Entity show cannot be null");
|
|
if (getHandle().connection == null) return;
|
|
if (equals(entity)) return;
|
|
|
|
boolean shouldShow;
|
|
if (entity.isVisibleByDefault()) {
|
|
shouldShow = removeInvertedVisibility(plugin, entity);
|
|
} else {
|
|
shouldShow = addInvertedVisibility(plugin, entity);
|
|
}
|
|
|
|
if (shouldShow) {
|
|
trackAndShowEntity(entity);
|
|
}
|
|
}
|
|
|
|
private boolean removeInvertedVisibility(@Nullable Plugin plugin, org.bukkit.entity.Entity entity) {
|
|
Set<WeakReference<Plugin>> invertedPlugins = invertedVisibilityEntities.get(entity.getUniqueId());
|
|
if (invertedPlugins == null) {
|
|
return false; // Entity isn't inverted
|
|
}
|
|
invertedPlugins.remove(getPluginWeakReference(plugin));
|
|
if (!invertedPlugins.isEmpty()) {
|
|
return false; // Some other plugins still want the entity inverted
|
|
}
|
|
invertedVisibilityEntities.remove(entity.getUniqueId());
|
|
|
|
return true;
|
|
}
|
|
|
|
private void trackAndShowEntity(org.bukkit.entity.Entity entity) {
|
|
PlayerChunkMap tracker = ((WorldServer) getHandle().level()).getChunkSource().chunkMap;
|
|
Entity other = ((CraftEntity) entity).getHandle();
|
|
|
|
if (other instanceof EntityPlayer) {
|
|
EntityPlayer otherPlayer = (EntityPlayer) other;
|
|
getHandle().connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(otherPlayer)));
|
|
}
|
|
|
|
PlayerChunkMap.EntityTracker entry = tracker.entityMap.get(other.getId());
|
|
if (entry != null && !entry.seenBy.contains(getHandle().connection)) {
|
|
entry.updatePlayer(getHandle());
|
|
}
|
|
|
|
server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity));
|
|
}
|
|
|
|
void resetAndShowEntity(org.bukkit.entity.Entity entity) {
|
|
// SPIGOT-7312: Can't show/hide self
|
|
if (equals(entity)) {
|
|
return;
|
|
}
|
|
|
|
if (invertedVisibilityEntities.remove(entity.getUniqueId()) == null) {
|
|
trackAndShowEntity(entity);
|
|
}
|
|
}
|
|
|
|
public void onEntityRemove(Entity entity) {
|
|
invertedVisibilityEntities.remove(entity.getUUID());
|
|
}
|
|
|
|
@Override
|
|
public boolean canSee(Player player) {
|
|
return canSee((org.bukkit.entity.Entity) player);
|
|
}
|
|
|
|
@Override
|
|
public boolean canSee(org.bukkit.entity.Entity entity) {
|
|
return equals(entity) || entity.isVisibleByDefault() ^ invertedVisibilityEntities.containsKey(entity.getUniqueId()); // SPIGOT-7312: Can always see self
|
|
}
|
|
|
|
public boolean canSee(UUID uuid) {
|
|
org.bukkit.entity.Entity entity = getServer().getPlayer(uuid);
|
|
if (entity == null) {
|
|
entity = getServer().getEntity(uuid); // Also includes players, but check players first for efficiency
|
|
}
|
|
|
|
return (entity != null) ? canSee(entity) : false; // If we can't find it, we can't see it
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> serialize() {
|
|
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
|
|
|
result.put("name", getName());
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Player getPlayer() {
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public EntityPlayer getHandle() {
|
|
return (EntityPlayer) entity;
|
|
}
|
|
|
|
public void setHandle(final EntityPlayer entity) {
|
|
super.setHandle(entity);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "CraftPlayer{" + "name=" + getName() + '}';
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
if (hash == 0 || hash == 485) {
|
|
hash = 97 * 5 + (this.getUniqueId() != null ? this.getUniqueId().hashCode() : 0);
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
@Override
|
|
public long getFirstPlayed() {
|
|
return firstPlayed;
|
|
}
|
|
|
|
@Override
|
|
public long getLastPlayed() {
|
|
return lastPlayed;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasPlayedBefore() {
|
|
return hasPlayedBefore;
|
|
}
|
|
|
|
public void setFirstPlayed(long firstPlayed) {
|
|
this.firstPlayed = firstPlayed;
|
|
}
|
|
|
|
public void readExtraData(NBTTagCompound nbttagcompound) {
|
|
hasPlayedBefore = true;
|
|
if (nbttagcompound.contains("bukkit")) {
|
|
NBTTagCompound data = nbttagcompound.getCompound("bukkit");
|
|
|
|
if (data.contains("firstPlayed")) {
|
|
firstPlayed = data.getLong("firstPlayed");
|
|
lastPlayed = data.getLong("lastPlayed");
|
|
}
|
|
|
|
if (data.contains("newExp")) {
|
|
EntityPlayer handle = getHandle();
|
|
handle.newExp = data.getInt("newExp");
|
|
handle.newTotalExp = data.getInt("newTotalExp");
|
|
handle.newLevel = data.getInt("newLevel");
|
|
handle.expToDrop = data.getInt("expToDrop");
|
|
handle.keepLevel = data.getBoolean("keepLevel");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setExtraData(NBTTagCompound nbttagcompound) {
|
|
if (!nbttagcompound.contains("bukkit")) {
|
|
nbttagcompound.put("bukkit", new NBTTagCompound());
|
|
}
|
|
|
|
NBTTagCompound data = nbttagcompound.getCompound("bukkit");
|
|
EntityPlayer handle = getHandle();
|
|
data.putInt("newExp", handle.newExp);
|
|
data.putInt("newTotalExp", handle.newTotalExp);
|
|
data.putInt("newLevel", handle.newLevel);
|
|
data.putInt("expToDrop", handle.expToDrop);
|
|
data.putBoolean("keepLevel", handle.keepLevel);
|
|
data.putLong("firstPlayed", getFirstPlayed());
|
|
data.putLong("lastPlayed", System.currentTimeMillis());
|
|
data.putString("lastKnownName", handle.getScoreboardName());
|
|
}
|
|
|
|
@Override
|
|
public boolean beginConversation(Conversation conversation) {
|
|
return conversationTracker.beginConversation(conversation);
|
|
}
|
|
|
|
@Override
|
|
public void abandonConversation(Conversation conversation) {
|
|
conversationTracker.abandonConversation(conversation, new ConversationAbandonedEvent(conversation, new ManuallyAbandonedConversationCanceller()));
|
|
}
|
|
|
|
@Override
|
|
public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) {
|
|
conversationTracker.abandonConversation(conversation, details);
|
|
}
|
|
|
|
@Override
|
|
public void acceptConversationInput(String input) {
|
|
conversationTracker.acceptConversationInput(input);
|
|
}
|
|
|
|
@Override
|
|
public boolean isConversing() {
|
|
return conversationTracker.isConversing();
|
|
}
|
|
|
|
@Override
|
|
public void sendPluginMessage(Plugin source, String channel, byte[] message) {
|
|
StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message);
|
|
if (getHandle().connection == null) return;
|
|
|
|
if (channels.contains(channel)) {
|
|
channel = StandardMessenger.validateAndCorrectChannel(channel);
|
|
PacketPlayOutCustomPayload packet = new PacketPlayOutCustomPayload(new MinecraftKey(channel), new PacketDataSerializer(Unpooled.wrappedBuffer(message)));
|
|
getHandle().connection.send(packet);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setTexturePack(String url) {
|
|
setResourcePack(url);
|
|
}
|
|
|
|
@Override
|
|
public void setResourcePack(String url) {
|
|
setResourcePack(url, null);
|
|
}
|
|
|
|
@Override
|
|
public void setResourcePack(String url, byte[] hash) {
|
|
setResourcePack(url, hash, false);
|
|
}
|
|
|
|
@Override
|
|
public void setResourcePack(String url, byte[] hash, String prompt) {
|
|
setResourcePack(url, hash, prompt, false);
|
|
}
|
|
|
|
@Override
|
|
public void setResourcePack(String url, byte[] hash, boolean force) {
|
|
setResourcePack(url, hash, null, force);
|
|
}
|
|
|
|
@Override
|
|
public void setResourcePack(String url, byte[] hash, String prompt, boolean force) {
|
|
Preconditions.checkArgument(url != null, "Resource pack URL cannot be null");
|
|
|
|
if (hash != null) {
|
|
Preconditions.checkArgument(hash.length == 20, "Resource pack hash should be 20 bytes long but was %s", hash.length);
|
|
|
|
getHandle().sendTexturePack(url, BaseEncoding.base16().lowerCase().encode(hash), force, CraftChatMessage.fromStringOrNull(prompt, true));
|
|
} else {
|
|
getHandle().sendTexturePack(url, "", force, CraftChatMessage.fromStringOrNull(prompt, true));
|
|
}
|
|
}
|
|
|
|
public void addChannel(String channel) {
|
|
Preconditions.checkState(channels.size() < 128, "Cannot register channel '%s'. Too many channels registered!", channel);
|
|
channel = StandardMessenger.validateAndCorrectChannel(channel);
|
|
if (channels.add(channel)) {
|
|
server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel));
|
|
}
|
|
}
|
|
|
|
public void removeChannel(String channel) {
|
|
channel = StandardMessenger.validateAndCorrectChannel(channel);
|
|
if (channels.remove(channel)) {
|
|
server.getPluginManager().callEvent(new PlayerUnregisterChannelEvent(this, channel));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Set<String> getListeningPluginChannels() {
|
|
return ImmutableSet.copyOf(channels);
|
|
}
|
|
|
|
public void sendSupportedChannels() {
|
|
if (getHandle().connection == null) return;
|
|
Set<String> listening = server.getMessenger().getIncomingChannels();
|
|
|
|
if (!listening.isEmpty()) {
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
|
|
for (String channel : listening) {
|
|
try {
|
|
stream.write(channel.getBytes("UTF8"));
|
|
stream.write((byte) 0);
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CraftPlayer.class.getName()).log(Level.SEVERE, "Could not send Plugin Channel REGISTER to " + getName(), ex);
|
|
}
|
|
}
|
|
|
|
getHandle().connection.send(new PacketPlayOutCustomPayload(new MinecraftKey("register"), new PacketDataSerializer(Unpooled.wrappedBuffer(stream.toByteArray()))));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public EntityType getType() {
|
|
return EntityType.PLAYER;
|
|
}
|
|
|
|
@Override
|
|
public void setMetadata(String metadataKey, MetadataValue newMetadataValue) {
|
|
server.getPlayerMetadata().setMetadata(this, metadataKey, newMetadataValue);
|
|
}
|
|
|
|
@Override
|
|
public List<MetadataValue> getMetadata(String metadataKey) {
|
|
return server.getPlayerMetadata().getMetadata(this, metadataKey);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasMetadata(String metadataKey) {
|
|
return server.getPlayerMetadata().hasMetadata(this, metadataKey);
|
|
}
|
|
|
|
@Override
|
|
public void removeMetadata(String metadataKey, Plugin owningPlugin) {
|
|
server.getPlayerMetadata().removeMetadata(this, metadataKey, owningPlugin);
|
|
}
|
|
|
|
@Override
|
|
public boolean setWindowProperty(Property prop, int value) {
|
|
Container container = getHandle().containerMenu;
|
|
if (container.getBukkitView().getType() != prop.getType()) {
|
|
return false;
|
|
}
|
|
container.setData(prop.getId(), value);
|
|
return true;
|
|
}
|
|
|
|
public void disconnect(String reason) {
|
|
conversationTracker.abandonAllConversations();
|
|
perm.clearPermissions();
|
|
}
|
|
|
|
@Override
|
|
public boolean isFlying() {
|
|
return getHandle().getAbilities().flying;
|
|
}
|
|
|
|
@Override
|
|
public void setFlying(boolean value) {
|
|
if (!getAllowFlight()) {
|
|
Preconditions.checkArgument(!value, "Player is not allowed to fly (check #getAllowFlight())");
|
|
}
|
|
|
|
getHandle().getAbilities().flying = value;
|
|
getHandle().onUpdateAbilities();
|
|
}
|
|
|
|
@Override
|
|
public boolean getAllowFlight() {
|
|
return getHandle().getAbilities().mayfly;
|
|
}
|
|
|
|
@Override
|
|
public void setAllowFlight(boolean value) {
|
|
if (isFlying() && !value) {
|
|
getHandle().getAbilities().flying = false;
|
|
}
|
|
|
|
getHandle().getAbilities().mayfly = value;
|
|
getHandle().onUpdateAbilities();
|
|
}
|
|
|
|
@Override
|
|
public int getNoDamageTicks() {
|
|
if (getHandle().spawnInvulnerableTime > 0) {
|
|
return Math.max(getHandle().spawnInvulnerableTime, getHandle().invulnerableTime);
|
|
} else {
|
|
return getHandle().invulnerableTime;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setNoDamageTicks(int ticks) {
|
|
super.setNoDamageTicks(ticks);
|
|
getHandle().spawnInvulnerableTime = ticks; // SPIGOT-5921: Update both for players, like the getter above
|
|
}
|
|
|
|
@Override
|
|
public void setFlySpeed(float value) {
|
|
validateSpeed(value);
|
|
EntityPlayer player = getHandle();
|
|
player.getAbilities().flyingSpeed = value / 2f;
|
|
player.onUpdateAbilities();
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setWalkSpeed(float value) {
|
|
validateSpeed(value);
|
|
EntityPlayer player = getHandle();
|
|
player.getAbilities().walkingSpeed = value / 2f;
|
|
player.onUpdateAbilities();
|
|
getHandle().getAttribute(GenericAttributes.MOVEMENT_SPEED).setBaseValue(player.getAbilities().walkingSpeed); // SPIGOT-5833: combination of the two in 1.16+
|
|
}
|
|
|
|
@Override
|
|
public float getFlySpeed() {
|
|
return (float) getHandle().getAbilities().flyingSpeed * 2f;
|
|
}
|
|
|
|
@Override
|
|
public float getWalkSpeed() {
|
|
return getHandle().getAbilities().walkingSpeed * 2f;
|
|
}
|
|
|
|
private void validateSpeed(float value) {
|
|
Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value);
|
|
}
|
|
|
|
@Override
|
|
public void setMaxHealth(double amount) {
|
|
super.setMaxHealth(amount);
|
|
this.health = Math.min(this.health, amount);
|
|
getHandle().resetSentInfo();
|
|
}
|
|
|
|
@Override
|
|
public void resetMaxHealth() {
|
|
super.resetMaxHealth();
|
|
getHandle().resetSentInfo();
|
|
}
|
|
|
|
@Override
|
|
public CraftScoreboard getScoreboard() {
|
|
return this.server.getScoreboardManager().getPlayerBoard(this);
|
|
}
|
|
|
|
@Override
|
|
public void setScoreboard(Scoreboard scoreboard) {
|
|
Preconditions.checkArgument(scoreboard != null, "Scoreboard cannot be null");
|
|
Preconditions.checkState(getHandle().connection != null, "Cannot set scoreboard yet (invalid player connection)");
|
|
Preconditions.checkState(getHandle().connection.isDisconnected(), "Cannot set scoreboard for invalid CraftPlayer (player is disconnected)");
|
|
|
|
this.server.getScoreboardManager().setPlayerBoard(this, scoreboard);
|
|
}
|
|
|
|
@Override
|
|
public void setHealthScale(double value) {
|
|
Preconditions.checkArgument(value > 0F, "Health value (%s) must be greater than 0", value);
|
|
healthScale = value;
|
|
scaledHealth = true;
|
|
updateScaledHealth();
|
|
}
|
|
|
|
@Override
|
|
public double getHealthScale() {
|
|
return healthScale;
|
|
}
|
|
|
|
@Override
|
|
public void setHealthScaled(boolean scale) {
|
|
if (scaledHealth != (scaledHealth = scale)) {
|
|
updateScaledHealth();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isHealthScaled() {
|
|
return scaledHealth;
|
|
}
|
|
|
|
public float getScaledHealth() {
|
|
return (float) (isHealthScaled() ? getHealth() * getHealthScale() / getMaxHealth() : getHealth());
|
|
}
|
|
|
|
@Override
|
|
public double getHealth() {
|
|
return health;
|
|
}
|
|
|
|
public void setRealHealth(double health) {
|
|
this.health = health;
|
|
}
|
|
|
|
public void updateScaledHealth() {
|
|
updateScaledHealth(true);
|
|
}
|
|
|
|
public void updateScaledHealth(boolean sendHealth) {
|
|
AttributeMapBase attributemapserver = getHandle().getAttributes();
|
|
Collection<AttributeModifiable> set = attributemapserver.getSyncableAttributes();
|
|
|
|
injectScaledMaxHealth(set, true);
|
|
|
|
// SPIGOT-3813: Attributes before health
|
|
if (getHandle().connection != null) {
|
|
getHandle().connection.send(new PacketPlayOutUpdateAttributes(getHandle().getId(), set));
|
|
if (sendHealth) {
|
|
sendHealthUpdate();
|
|
}
|
|
}
|
|
getHandle().getEntityData().set(EntityLiving.DATA_HEALTH_ID, (float) getScaledHealth());
|
|
|
|
getHandle().maxHealthCache = getMaxHealth();
|
|
}
|
|
|
|
public void sendHealthUpdate() {
|
|
getHandle().connection.send(new PacketPlayOutUpdateHealth(getScaledHealth(), getHandle().getFoodData().getFoodLevel(), getHandle().getFoodData().getSaturationLevel()));
|
|
}
|
|
|
|
public void injectScaledMaxHealth(Collection<AttributeModifiable> collection, boolean force) {
|
|
if (!scaledHealth && !force) {
|
|
return;
|
|
}
|
|
for (AttributeModifiable genericInstance : collection) {
|
|
if (genericInstance.getAttribute() == GenericAttributes.MAX_HEALTH) {
|
|
collection.remove(genericInstance);
|
|
break;
|
|
}
|
|
}
|
|
AttributeModifiable dummy = new AttributeModifiable(GenericAttributes.MAX_HEALTH, (attribute) -> { });
|
|
dummy.setBaseValue(scaledHealth ? healthScale : getMaxHealth());
|
|
collection.add(dummy);
|
|
}
|
|
|
|
@Override
|
|
public org.bukkit.entity.Entity getSpectatorTarget() {
|
|
Entity followed = getHandle().getCamera();
|
|
return followed == getHandle() ? null : followed.getBukkitEntity();
|
|
}
|
|
|
|
@Override
|
|
public void setSpectatorTarget(org.bukkit.entity.Entity entity) {
|
|
Preconditions.checkArgument(getGameMode() == GameMode.SPECTATOR, "Player must be in spectator mode");
|
|
getHandle().setCamera((entity == null) ? null : ((CraftEntity) entity).getHandle());
|
|
}
|
|
|
|
@Override
|
|
public void sendTitle(String title, String subtitle) {
|
|
sendTitle(title, subtitle, 10, 70, 20);
|
|
}
|
|
|
|
@Override
|
|
public void sendTitle(String title, String subtitle, int fadeIn, int stay, int fadeOut) {
|
|
ClientboundSetTitlesAnimationPacket times = new ClientboundSetTitlesAnimationPacket(fadeIn, stay, fadeOut);
|
|
getHandle().connection.send(times);
|
|
|
|
if (title != null) {
|
|
ClientboundSetTitleTextPacket packetTitle = new ClientboundSetTitleTextPacket(CraftChatMessage.fromString(title)[0]);
|
|
getHandle().connection.send(packetTitle);
|
|
}
|
|
|
|
if (subtitle != null) {
|
|
ClientboundSetSubtitleTextPacket packetSubtitle = new ClientboundSetSubtitleTextPacket(CraftChatMessage.fromString(subtitle)[0]);
|
|
getHandle().connection.send(packetSubtitle);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void resetTitle() {
|
|
ClientboundClearTitlesPacket packetReset = new ClientboundClearTitlesPacket(true);
|
|
getHandle().connection.send(packetReset);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, Location location, int count) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, double x, double y, double z, int count) {
|
|
spawnParticle(particle, x, y, z, count, null);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, Location location, int count, T data) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, data);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, T data) {
|
|
spawnParticle(particle, x, y, z, count, 0, 0, 0, data);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ) {
|
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, null);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T data) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, data);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T data) {
|
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, 1, data);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra);
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra) {
|
|
spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) {
|
|
spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data);
|
|
}
|
|
|
|
@Override
|
|
public <T> void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) {
|
|
if (data != null) {
|
|
Preconditions.checkArgument(particle.getDataType().isInstance(data), "data (%s) should be %s", data.getClass(), particle.getDataType());
|
|
}
|
|
PacketPlayOutWorldParticles packetplayoutworldparticles = new PacketPlayOutWorldParticles(CraftParticle.toNMS(particle, data), true, (float) x, (float) y, (float) z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count);
|
|
getHandle().connection.send(packetplayoutworldparticles);
|
|
|
|
}
|
|
|
|
@Override
|
|
public org.bukkit.advancement.AdvancementProgress getAdvancementProgress(org.bukkit.advancement.Advancement advancement) {
|
|
Preconditions.checkArgument(advancement != null, "advancement");
|
|
|
|
CraftAdvancement craft = (CraftAdvancement) advancement;
|
|
AdvancementDataPlayer data = getHandle().getAdvancements();
|
|
AdvancementProgress progress = data.getOrStartProgress(craft.getHandle());
|
|
|
|
return new CraftAdvancementProgress(craft, data, progress);
|
|
}
|
|
|
|
@Override
|
|
public int getClientViewDistance() {
|
|
return (getHandle().clientViewDistance == null) ? Bukkit.getViewDistance() : getHandle().clientViewDistance;
|
|
}
|
|
|
|
@Override
|
|
public int getPing() {
|
|
return getHandle().latency;
|
|
}
|
|
|
|
@Override
|
|
public String getLocale() {
|
|
return getHandle().locale;
|
|
}
|
|
|
|
@Override
|
|
public void updateCommands() {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().server.getCommands().sendCommands(getHandle());
|
|
}
|
|
|
|
@Override
|
|
public void openBook(ItemStack book) {
|
|
Preconditions.checkArgument(book != null, "ItemStack cannot be null");
|
|
Preconditions.checkArgument(book.getType() == Material.WRITTEN_BOOK, "ItemStack Material (%s) must be Material.WRITTEN_BOOK", book.getType());
|
|
|
|
ItemStack hand = getInventory().getItemInMainHand();
|
|
getInventory().setItemInMainHand(book);
|
|
getHandle().openItemGui(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(book), net.minecraft.world.EnumHand.MAIN_HAND);
|
|
getInventory().setItemInMainHand(hand);
|
|
}
|
|
|
|
@Override
|
|
public void openSign(Sign sign) {
|
|
openSign(sign, Side.FRONT);
|
|
}
|
|
|
|
@Override
|
|
public void openSign(@NotNull Sign sign, @NotNull Side side) {
|
|
CraftSign.openSign(sign, this, side);
|
|
}
|
|
|
|
@Override
|
|
public void showDemoScreen() {
|
|
if (getHandle().connection == null) return;
|
|
|
|
getHandle().connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.DEMO_EVENT, PacketPlayOutGameStateChange.DEMO_PARAM_INTRO));
|
|
}
|
|
|
|
@Override
|
|
public boolean isAllowingServerListings() {
|
|
return getHandle().allowsListing();
|
|
}
|
|
}
|