Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from GrowlyX/main
Add v1_8_R1 NMS support
- Loading branch information
Showing
4 changed files
with
368 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<parent> | ||
<artifactId>TabAPI</artifactId> | ||
<groupId>io.github.nosequel.tab</groupId> | ||
<version>1.1-SNAPSHOT</version> | ||
</parent> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<artifactId>v1_8_R1</artifactId> | ||
|
||
<properties> | ||
<maven.compiler.source>8</maven.compiler.source> | ||
<maven.compiler.target>8</maven.compiler.target> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.github.nosequel.tab</groupId> | ||
<artifactId>shared</artifactId> | ||
<version>${parent.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>org.spigotmc</groupId> | ||
<artifactId>spigot</artifactId> | ||
<version>1.0-SNAPSHOT</version> | ||
<scope>system</scope> | ||
<systemPath>${pom.basedir}/spigot/spigot.jar</systemPath> | ||
</dependency> | ||
</dependencies> | ||
</project> |
Binary file not shown.
332 changes: 332 additions & 0 deletions
332
v1_8_R1/src/main/java/io/github/nosequel/tab/v1_8_r1/v1_8_R1TabAdapter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,332 @@ | ||
package io.github.nosequel.tab.v1_8_r1; | ||
|
||
import com.mojang.authlib.GameProfile; | ||
import com.mojang.authlib.properties.Property; | ||
import io.github.nosequel.tab.shared.TabAdapter; | ||
import io.github.nosequel.tab.shared.client.ClientVersionUtil; | ||
import io.github.nosequel.tab.shared.skin.SkinType; | ||
import io.netty.channel.ChannelDuplexHandler; | ||
import io.netty.channel.ChannelHandlerContext; | ||
import io.netty.channel.ChannelPromise; | ||
import net.minecraft.server.v1_8_R1.*; | ||
import org.bukkit.Bukkit; | ||
import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer; | ||
import org.bukkit.entity.Player; | ||
|
||
import java.lang.reflect.Field; | ||
import java.util.*; | ||
|
||
public class v1_8_R1TabAdapter extends TabAdapter { | ||
|
||
private final Map<Player, GameProfile[]> profiles = new HashMap<>(); | ||
private final List<Player> initialized = new ArrayList<>(); | ||
|
||
/** | ||
* Send a packet to the player | ||
* | ||
* @param player the player | ||
* @param packet the packet to send | ||
*/ | ||
private void sendPacket(Player player, Packet packet) { | ||
((CraftPlayer) player).getHandle().playerConnection.sendPacket(packet); | ||
} | ||
|
||
|
||
/** | ||
* Create a new game profile | ||
* | ||
* @param index the index of the profile | ||
* @param text the text to display | ||
* @param player the player to make the profiles for | ||
*/ | ||
@Override | ||
public void createProfiles(int index, String text, Player player) { | ||
if (!this.profiles.containsKey(player)) { | ||
this.profiles.put(player, new GameProfile[80]); | ||
} | ||
|
||
if (this.profiles.get(player).length < index+1 || this.profiles.get(player)[index] == null) { | ||
final GameProfile profile = new GameProfile(UUID.randomUUID(), text); | ||
final String[] skinData = SkinType.DARK_GRAY.getSkinData(); | ||
|
||
profile.getProperties().put("textures", new Property("textures", skinData[0], skinData[1])); | ||
|
||
this.profiles.get(player)[index] = profile; | ||
} | ||
} | ||
|
||
/** | ||
* Send the header and footer to a player | ||
* | ||
* @param player the player to send the header and footer to | ||
* @param header the header to send | ||
* @param footer the footer to send | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter sendHeaderFooter(Player player, String header, String footer) { | ||
if (this.getMaxElements(player) > 60 && (header != null || footer != null)) { | ||
final Packet packet = new PacketPlayOutPlayerListHeaderFooter( | ||
ChatSerializer.a("{\"text\":\"" + header + "\"}") | ||
); | ||
|
||
try { | ||
final Field footerField = packet.getClass().getDeclaredField("b"); | ||
|
||
footerField.setAccessible(true); | ||
footerField.set(packet, ChatSerializer.a("{\"text\":\"" + footer + "\"}")); | ||
} catch (IllegalAccessException | NoSuchFieldException e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
this.sendPacket(player, packet); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
|
||
/** | ||
* Update the skin on the tablist for a player | ||
* | ||
* @param skinData the data of the new skin | ||
* @param index the index of the profile | ||
* @param player the player to update the skin for | ||
*/ | ||
@Override | ||
public void updateSkin(String[] skinData, int index, Player player) { | ||
final GameProfile profile = this.profiles.get(player)[index]; | ||
final Property property = profile.getProperties().get("textures").iterator().next(); | ||
final EntityPlayer entityPlayer = this.getEntityPlayer(profile); | ||
|
||
skinData = skinData != null && skinData.length >= 1 && !skinData[0].isEmpty() && !skinData[1].isEmpty() | ||
? skinData | ||
: SkinType.DARK_GRAY.getSkinData(); | ||
|
||
if (!property.getSignature().equals(skinData[1]) || !property.getValue().equals(skinData[0])) { | ||
profile.getProperties().remove("textures", property); | ||
profile.getProperties().put("textures", new Property("textures", skinData[0], skinData[1])); | ||
|
||
this.sendInfoPacket(player, EnumPlayerInfoAction.ADD_PLAYER, entityPlayer); | ||
} | ||
} | ||
|
||
/** | ||
* Check if the player should be able to see the fourth row | ||
* | ||
* @param player the player | ||
* @return whether they should be able to see the fourth row | ||
*/ | ||
@Override | ||
public int getMaxElements(Player player) { | ||
final int version = ClientVersionUtil.getProtocolVersion(player); | ||
|
||
return version == -1 || version > 5 ? 80 : 60; | ||
} | ||
|
||
/** | ||
* Send an entry's data to a player | ||
* | ||
* @param player the player | ||
* @param axis the axis of the entry | ||
* @param ping the ping to display on the entry's position | ||
* @param text the text to display on the entry's position | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter sendEntryData(Player player, int axis, int ping, String text) { | ||
final GameProfile profile = this.profiles.get(player)[axis]; | ||
final EntityPlayer entityPlayer = this.getEntityPlayer(profile); | ||
|
||
entityPlayer.ping = ping; | ||
|
||
this.setupScoreboard(player, text, profile.getName()); | ||
|
||
if (this.getMaxElements(player) == 80) { | ||
entityPlayer.listName = new ChatComponentText(text); | ||
this.sendInfoPacket(player, EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entityPlayer); | ||
} | ||
|
||
this.sendInfoPacket(player, EnumPlayerInfoAction.UPDATE_LATENCY, entityPlayer); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Add fake players to the player's tablist | ||
* | ||
* @param player the player to send the fake players to | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter addFakePlayers(Player player) { | ||
if(!this.initialized.contains(player)) { | ||
for (int i = 0; i < this.getMaxElements(player); i++) { | ||
final GameProfile profile = this.profiles.get(player)[i]; | ||
final EntityPlayer entityPlayer = this.getEntityPlayer(profile); | ||
|
||
this.sendInfoPacket(player, EnumPlayerInfoAction.ADD_PLAYER, entityPlayer); | ||
} | ||
|
||
this.initialized.add(player); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Get an entity player by a profile | ||
* | ||
* @param profile the profile | ||
* @return the entity player | ||
*/ | ||
private EntityPlayer getEntityPlayer(GameProfile profile) { | ||
final MinecraftServer server = MinecraftServer.getServer(); | ||
final PlayerInteractManager interactManager = new PlayerInteractManager(server.getWorldServer(0)); | ||
|
||
return new EntityPlayer(server, server.getWorldServer(0), profile, interactManager); | ||
} | ||
|
||
/** | ||
* Hide all real players from the tab | ||
* | ||
* @param player the player | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter hideRealPlayers(Player player) { | ||
for (Player target : Bukkit.getOnlinePlayers()) { | ||
this.hidePlayer(player, target); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Hide a real player om the tablist | ||
* | ||
* @param player the player to hide the player from | ||
* @param target the player to hide | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter hidePlayer(Player player, Player target) { | ||
this.sendInfoPacket(player, EnumPlayerInfoAction.REMOVE_PLAYER, target); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Show all real players on the tab | ||
* | ||
* @param player the player | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter showRealPlayers(Player player) { | ||
if (!this.initialized.contains(player)) { | ||
// don't really know what to do here as | ||
// network manager's i field is a private field. | ||
|
||
// reflection would work but cba to do rn | ||
|
||
// final ChannelPipeline pipeline = this.getPlayerConnection(player).networkManager.i(); | ||
// | ||
// pipeline.addBefore( | ||
// "packet_handler", | ||
// player.getName(), | ||
// this.createShowListener(player) | ||
// ); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Create the listener required to show the players | ||
* | ||
* @param player the player to create it for | ||
* @return the handler | ||
*/ | ||
private ChannelDuplexHandler createShowListener(Player player) { | ||
return new ChannelDuplexHandler() { | ||
@Override | ||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise promise) throws Exception { | ||
if (packet instanceof PacketPlayOutNamedEntitySpawn) { | ||
final PacketPlayOutNamedEntitySpawn entitySpawn = (PacketPlayOutNamedEntitySpawn) packet; | ||
final Field uuidField = entitySpawn.getClass().getDeclaredField("b"); | ||
|
||
uuidField.setAccessible(true); | ||
|
||
final Player target = Bukkit.getPlayer((UUID) uuidField.get(entitySpawn)); | ||
|
||
if (target != null) { | ||
showPlayer(player, target); | ||
} | ||
} else if (packet instanceof PacketPlayOutRespawn) { | ||
showPlayer(player, player); | ||
} | ||
|
||
super.write(context, packet, promise); | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Show a real player to a player | ||
* | ||
* @param player the player | ||
* @param target the player to show to the other player | ||
* @return the current adapter instance | ||
*/ | ||
@Override | ||
public TabAdapter showPlayer(Player player, Player target) { | ||
this.sendInfoPacket(player, EnumPlayerInfoAction.ADD_PLAYER, target); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Get the {@link PlayerConnection} of a player | ||
* | ||
* @param player the player to get the player connection object from | ||
* @return the object | ||
*/ | ||
private PlayerConnection getPlayerConnection(Player player) { | ||
return this.getEntityPlayer(player).playerConnection; | ||
} | ||
|
||
/** | ||
* Send the {@link PacketPlayOutPlayerInfo} to a player | ||
* | ||
* @param player the player | ||
* @param action the action | ||
* @param target the target | ||
*/ | ||
private void sendInfoPacket(Player player, EnumPlayerInfoAction action, EntityPlayer target) { | ||
this.sendPacket(player, new PacketPlayOutPlayerInfo(action, target)); | ||
} | ||
|
||
/** | ||
* Send the {@link PacketPlayOutPlayerInfo} to a player | ||
* | ||
* @param player the player | ||
* @param action the action | ||
* @param target the target | ||
*/ | ||
private void sendInfoPacket(Player player, EnumPlayerInfoAction action, Player target) { | ||
this.sendInfoPacket(player, action, ((CraftPlayer) target).getHandle()); | ||
} | ||
|
||
/** | ||
* Get the {@link EntityPlayer} object of a player | ||
* | ||
* @param player the player to get the entity player object from | ||
* @return the entity player | ||
*/ | ||
private EntityPlayer getEntityPlayer(Player player) { | ||
return ((CraftPlayer) player).getHandle(); | ||
} | ||
|
||
|
||
} |