package tc.oc.commons.bukkit.tablist; import java.util.UUID; import javax.annotation.Nullable; import javax.inject.Inject; import net.md_5.bungee.api.chat.BaseComponent; import org.bukkit.Skin; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerSkinPartsChangeEvent; import tc.oc.commons.bukkit.chat.NameStyle; import tc.oc.commons.bukkit.chat.PlayerComponent; import tc.oc.commons.bukkit.nick.Identity; import tc.oc.commons.bukkit.nick.IdentityProvider; import tc.oc.commons.bukkit.nick.PlayerIdentityChangeEvent; import tc.oc.commons.core.util.DefaultProvider; /** * {@link TabEntry} showing a {@link Player}'s name and skin. * * Note that this is NOT the player's real entry. It has a random UUID and name, * like any other {@link SimpleTabEntry}. While this entry is visible in a {@link TabView}, * a fake player entity will be spawned with a copy of the real player's metadata. */ public class PlayerTabEntry extends DynamicTabEntry { public static class Factory implements DefaultProvider<Player, PlayerTabEntry> { @Override public PlayerTabEntry get(Player key) { return new PlayerTabEntry(key); } } private static UUID randomUUIDVersion2SameDefaultSkin(UUID original) { // Parity of UUID.hashCode determines if the player's default skin is Steve/Alex // To make the player list match, we generate a random UUID with the same hashCode parity. // UUID.hashCode returns the XOR of its four 32-bit segments, so set bit 0 to the desired // parity, and clear bits 32, 64, and 96 long parity = original.hashCode() & 1L; long mask = ~((1L << 32) | 1L); UUID uuid = randomUUIDVersion2(); uuid = new UUID(uuid.getMostSignificantBits() & mask, (uuid.getLeastSignificantBits() & mask) | parity); return uuid; } @Inject private static IdentityProvider identityProvider; protected final Player player; protected @Nullable PlayerComponent content; private final int spareEntityId; public PlayerTabEntry(Player player) { super(randomUUIDVersion2SameDefaultSkin(player.getUniqueId())); this.player = player; this.spareEntityId = player.getServer().allocateEntityId(); } @Override public BaseComponent getContent(TabView view) { if(content == null) { this.content = new PlayerComponent(identityProvider.currentIdentity(player), NameStyle.GAME); } return content; } @Override public int getFakeEntityId(TabView view) { return this.spareEntityId; } @Override public Player getFakePlayer(TabView view) { return this.player; } @Override public @Nullable Skin getSkin(TabView view) { final Identity identity = identityProvider.currentIdentity(player); return identity.isDisguised(view.getViewer()) ? null : player.getSkin(); } // Dispatched by TabManager protected void onNickChange(PlayerIdentityChangeEvent event) { if(this.player == event.getPlayer()) { // PlayerComponents are bound to an Identity, and we always want to show // the player's current Identity, so we have to replace the PlayerComponent // when the player's identity changes. this.content = null; this.invalidate(); this.refresh(); } } // Dispatched by TabManager protected void onSkinPartsChange(PlayerSkinPartsChangeEvent event) { if(this.player == event.getPlayer()) { this.updateFakeEntity(); } } @Override public String toString() { return this.getClass().getSimpleName() + "{" + this.player.getName() + "}"; } }