package tc.oc.commons.bukkit.tablist; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.inject.Inject; import net.md_5.bungee.api.chat.BaseComponent; import net.minecraft.server.Packet; import net.minecraft.server.PacketPlayOutPlayerInfo; import org.bukkit.Location; import org.bukkit.entity.Player; import tc.oc.commons.bukkit.chat.ComponentRenderContext; import tc.oc.commons.bukkit.util.NMSHacks; public class TabRender { @Inject private static ComponentRenderContext componentRenderContext; private final TabView view; private final PacketPlayOutPlayerInfo removePacket; private final PacketPlayOutPlayerInfo addPacket; private final PacketPlayOutPlayerInfo updatePacket; private final List<Packet> deferredPackets; public TabRender(TabView view) { this.view = view; this.removePacket = this.createPlayerInfoPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER); this.addPacket = this.createPlayerInfoPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER); this.updatePacket = this.createPlayerInfoPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME); this.deferredPackets = new ArrayList<>(); } private String teamName(int slot) { return "\u0001TabView" + String.format("%03d", slot); } private void send(Packet packet) { NMSHacks.sendPacket(this.view.getViewer(), packet); } private PacketPlayOutPlayerInfo createPlayerInfoPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action) { return new PacketPlayOutPlayerInfo(action); } private BaseComponent getContent(TabEntry entry, int index) { return this.componentRenderContext.render(entry.getContent(this.view), this.view.getViewer()); } private void appendAddition(TabEntry entry, int index) { BaseComponent displayName = this.getContent(entry, index); this.addPacket.add(NMSHacks.playerListPacketData(this.addPacket, entry.getId(), entry.getName(this.view), displayName, entry.getGamemode(), entry.getPing(), entry.getSkin(this.view))); // Due to a client bug, display name is ignored in ADD_PLAYER packets, // so we have to send an UPDATE_DISPLAY_NAME afterward. this.updatePacket.add(NMSHacks.playerListPacketData(this.updatePacket, entry.getId(), displayName)); this.updateFakeEntity(entry, true); } private void appendRemoval(TabEntry entry) { this.removePacket.add(NMSHacks.playerListPacketData(this.removePacket, entry.getId())); int entityId = entry.getFakeEntityId(this.view); if(entityId >= 0) { this.send(NMSHacks.destroyEntitiesPacket(entityId)); } } private void leaveSlot(TabEntry entry, int index) { this.send(NMSHacks.teamLeavePacket(this.teamName(index), Collections.singleton(entry.getName(this.view)))); } private void joinSlot(TabEntry entry, int index) { this.send(NMSHacks.teamJoinPacket(this.teamName(index), Collections.singleton(entry.getName(this.view)))); } public void finish() { if(!this.removePacket.isEmpty()) this.send(this.removePacket); if(!this.addPacket.isEmpty()) this.send(this.addPacket); if(!this.updatePacket.isEmpty()) this.send(this.updatePacket); for(Packet packet : this.deferredPackets) { this.send(packet); } } public void changeSlot(TabEntry entry, int oldIndex, int newIndex) { Collection<String> names = Collections.singleton(entry.getName(this.view)); this.send(NMSHacks.teamJoinPacket(this.teamName(newIndex), names)); } public void createSlot(TabEntry entry, int index) { String teamName = this.teamName(index); this.send(NMSHacks.teamCreatePacket(teamName, teamName, "", "", false, false, Collections.singleton(entry.getName(this.view)))); this.appendAddition(entry, index); } public void destroySlot(TabEntry entry, int index) { this.send(NMSHacks.teamRemovePacket(this.teamName(index))); this.appendRemoval(entry); } public void addEntry(TabEntry entry, int index) { this.joinSlot(entry, index); this.appendAddition(entry, index); } public void removeEntry(TabEntry entry, int index) { this.leaveSlot(entry, index); this.appendRemoval(entry); } public void refreshEntry(TabEntry entry, int index) { this.appendRemoval(entry); this.appendAddition(entry, index); } public void updateEntry(TabEntry entry, int index) { this.updatePacket.add(NMSHacks.playerListPacketData(this.updatePacket, entry.getId(), this.getContent(entry, index))); } public void setHeaderFooter(TabEntry header, TabEntry footer) { view.getViewer().setPlayerListHeaderFooter(componentRenderContext.render(header.getContent(view), view.getViewer()), componentRenderContext.render(footer.getContent(view), view.getViewer())); } public void updateFakeEntity(TabEntry entry, boolean create) { Player player = entry.getFakePlayer(this.view); if(player != null) { int entityId = entry.getFakeEntityId(this.view); if(create) { this.deferredPackets.add(NMSHacks.spawnPlayerPacket( entityId, entry.getId(), new Location(this.view.getViewer().getWorld(), 0, Integer.MAX_VALUE / 2, 0, 0, 0), player )); } else { this.deferredPackets.add(NMSHacks.entityMetadataPacket(entityId, player, true)); } } } }