Skip to content

Commit

Permalink
Push dev branch to main (#8)
Browse files Browse the repository at this point in the history
* Removed unnecessary section wrapping of sortings and replaced with list format

* Created base PacketListener to handle interception of TAB list

* Loaded PacketListener

* Added write packet interception and recognition of update info packet

* Added filtering of game mode enum by spectator

* Added replacement logic

* Completed first working implementation of replacing packet

* Switched FoliaTpsProvider system to provide global TPS

* Added hide-spectator-mode-self option

* Implemented hide-spectator-mode-self option

* Fixed constructor call

* Fixed invalid uuid retrieval

* Extracted method should hide self
Fixed some inconsistencies

* Removed hide-spectator-self option

* Set the folia TPS provider to 15 seconds

---------

Co-authored-by: Netherwhal <[email protected]>
  • Loading branch information
Fulminazzo and Netherwhal authored Mar 9, 2024
1 parent ee8dc94 commit 764367e
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.exception.NoPacketAdapterAvailableException;
import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary;
import net.simplyvanilla.simplytab.packets.PacketListener;
import net.simplyvanilla.simplytab.rank.TablistRankHandler;
import net.simplyvanilla.simplytab.tab.TablistManager;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public class SimplyTabPlugin extends JavaPlugin {

private ScoreboardLibrary scoreboardLibrary;
private PacketListener packetListener;

@Override
public void onEnable() {
Expand All @@ -25,10 +28,15 @@ public void onEnable() {

new TablistManager(this);
new TablistRankHandler(this);

this.packetListener = new PacketListener();
this.packetListener.loadAll();
Bukkit.getPluginManager().registerEvents(this.packetListener, this);
}

@Override
public void onDisable() {
if (this.packetListener != null) this.packetListener.unloadAll();
this.scoreboardLibrary.close();
}

Expand Down
164 changes: 164 additions & 0 deletions src/main/java/net/simplyvanilla/simplytab/packets/PacketListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package net.simplyvanilla.simplytab.packets;

import io.netty.channel.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;

public class PacketListener implements Listener {
private static final String UPDATE_INFO_PACKET = "ClientboundPlayerInfoUpdatePacket";
private static final String UPDATE_GAME_MODE_PACKET = "UPDATE_GAME_MODE";
private static final String GAME_MODE_ENUM_NAME = "EnumGamemode";
private static final String SPECTATOR_ENUM_NAME = "SPECTATOR";
private static final String REPLACEMENT_ENUM = "c"; // ADVENTURE

@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
injectPlayer(event.getPlayer());
}

@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
removePlayer(event.getPlayer());
}

public void loadAll() {
Bukkit.getOnlinePlayers().forEach(this::injectPlayer);
}

public void unloadAll() {
Bukkit.getOnlinePlayers().forEach(this::removePlayer);
}

private void injectPlayer(Player player) {
ChannelDuplexHandler channelDuplexHandler = new ChannelDuplexHandler() {

@Override
public void write(ChannelHandlerContext channelHandlerContext, Object packet, ChannelPromise promise) throws Exception {
try {
if (!packet.toString().contains(UPDATE_INFO_PACKET)) return;

EnumSet<?> actions = getFieldObject(packet, EnumSet.class.getSimpleName());
List<Object> previousEntries = getFieldObject(packet, List.class.getSimpleName());
List<Object> entries = new LinkedList<>(previousEntries);
if (actions.stream().noneMatch(p -> p.name().equals(UPDATE_GAME_MODE_PACKET))) return;

for (int i = 0; i < entries.size(); i++) {
Object entry = entries.get(i);

Field gameTypeField = getField(entry, GAME_MODE_ENUM_NAME);
Enum<?> gameType = (Enum<?>) gameTypeField.get(entry);
if (gameType == null) continue;
if (!gameType.name().equals(SPECTATOR_ENUM_NAME)) continue;

UUID uuid = getFieldObject(entry, UUID.class.getSimpleName());
if (player.getUniqueId().equals(uuid)) continue;

Class<?> clazz = gameType.getClass();
Field replaceField = clazz.getDeclaredField(REPLACEMENT_ENUM);
replaceField.setAccessible(true);
Enum<?> replacement = (Enum<?>) replaceField.get(clazz);

Class<?> entryClass = entry.getClass();
Field[] fields = entryClass.getDeclaredFields();
Object[] objects = Arrays.stream(fields).peek(f -> f.setAccessible(true)).map(f -> {
try {
if (f.getType().getSimpleName().equals(GAME_MODE_ENUM_NAME)) return replacement;
return f.get(entry);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}).toList().toArray(new Object[0]);
Class<?>[] classes = Arrays.stream(fields).map(Field::getType).toList().toArray(new Class[0]);

Constructor<?> entryConstructor = entryClass.getDeclaredConstructor(classes);
Object newEntry = entryConstructor.newInstance(objects);
entries.set(i, newEntry);
}

if (previousEntries.equals(entries)) return;

Class<?> packetClass = packet.getClass();
Constructor<?> constructor = packetClass.getDeclaredConstructor(EnumSet.class, List.class);
constructor.setAccessible(true);
packet = constructor.newInstance(actions, entries);

} finally {
super.write(channelHandlerContext, packet, promise);
}
}

};

ChannelPipeline pipeline = getPlayerChannel(player).pipeline();
pipeline.addBefore("packet_handler", player.getName(), channelDuplexHandler);
}

private void removePlayer(Player player) {
Channel channel = getPlayerChannel(player);
channel.eventLoop().submit(() -> channel.pipeline().remove(player.getName()));
}

private Channel getPlayerChannel(Player player) {
try {
if (player == null) throw new NullPointerException("Player cannot be null");

// ((CraftPlayer) player).getHandle()
Method getHandle = Arrays.stream(player.getClass().getMethods())
.filter(m -> m.getParameterCount() == 0)
.filter(m -> m.getReturnType().getSimpleName().equals("EntityPlayer"))
.findFirst().orElse(null);
if (getHandle == null)
throw new NoSuchMethodException(String.format("Could not find method EntityPlayer() in %s", player.getClass().getCanonicalName()));
Object handle = getHandle.invoke(player);

// handle.playerConnection
Object playerConnection = getFieldObject(handle, "PlayerConnection");

// playerConnection.networkManager
Object networkManager = getFieldObject(playerConnection, "NetworkManager");

// networkManager.channel
return getFieldObject(networkManager, "Channel");
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
else throw new RuntimeException(e);
}
}

@SuppressWarnings("unchecked")
private <T> T getFieldObject(Object object, String typeName) {
try {
Field field = getField(object, typeName);
return (T) field.get(object);
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
}
}

private Field getField(Object object, String typeName) {
try {
Field field = Stream.concat(Arrays.stream(object.getClass().getFields()),
Arrays.stream(object.getClass().getDeclaredFields()))
.filter(f -> f.getType().getSimpleName().equals(typeName))
.findFirst().orElse(null);
if (field == null)
throw new NoSuchFieldException(String.format("Could not find field %s in %s", typeName, object.getClass().getCanonicalName()));
field.setAccessible(true);
return field;
} catch (Exception e) {
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

import java.util.ArrayList;
import java.util.List;
import java.util.*;

public class TablistRankHandler implements Listener {
private final SimplyTabPlugin plugin;
Expand Down Expand Up @@ -76,49 +75,49 @@ public void createTab(Player player) {
}

private String getTeamName(Player onlinePlayer) {
ConfigurationSection sortings = this.plugin.getConfig().getConfigurationSection("sortings");
List<Map<String, Object>> tmp = (List<Map<String, Object>>) this.plugin.getConfig().getList("sortings");

int sortingId = 0;
for (String key : sortings.getKeys(false)) {
var section = sortings.getConfigurationSection(key);
var type = section.getString("type");

switch (type.toLowerCase()) {
case "index":
var placeholder = section.getString("placeholder", "");
var resolvedValue = PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(onlinePlayer))
);
var values = section.getStringList("values");
int index = values.indexOf(resolvedValue);
if (index == -1) {
index = values.size() + 1;
}
sortingId += index * 100;
break;
case "alphabetical":
placeholder = section.getString("placeholder", "");
resolvedValue = PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(onlinePlayer))
);

List<String> playerValues = new ArrayList<>(Bukkit.getOnlinePlayers().stream()
.map(player -> PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(player))
)).toList());

// sort player values alphabetically
playerValues.sort(String::compareToIgnoreCase);
index = playerValues.indexOf(resolvedValue);
if (index == -1) {
index = playerValues.size() + 1;
}
sortingId += index;
break;
default:
break;
if (tmp != null)
for (var section : tmp) {
var type = (String) section.get("type");

switch (type.toLowerCase()) {
case "index":
var placeholder = (String) section.getOrDefault("placeholder", "");
var resolvedValue = PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(onlinePlayer))
);
var values = (List<String>) section.get("values");
int index = values.indexOf(resolvedValue);
if (index == -1) {
index = values.size() + 1;
}
sortingId += index * 100;
break;
case "alphabetical":
placeholder = (String) section.getOrDefault("placeholder", "");
resolvedValue = PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(onlinePlayer))
);

List<String> playerValues = new ArrayList<>(Bukkit.getOnlinePlayers().stream()
.map(player -> PlainTextComponentSerializer.plainText().serialize(
MiniMessage.miniMessage().deserialize(placeholder, MiniPlaceholders.getAudiencePlaceholders(player))
)).toList());

// sort player values alphabetically
playerValues.sort(String::compareToIgnoreCase);
index = playerValues.indexOf(resolvedValue);
if (index == -1) {
index = playerValues.size() + 1;
}
sortingId += index;
break;
default:
break;
}
}
}
// create team name like "0001_name"
String formattedName = String.format("%05d_%s", sortingId, onlinePlayer.getName());
// team name can only be 16 characters long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

import io.papermc.paper.threadedregions.TickData;
import io.papermc.paper.threadedregions.TickRegionScheduler;
import org.bukkit.Bukkit;

public class FoliaTpsProvider implements TpsProvider {
@Override
public double getTps() {

TickData.TickReportData report = TickRegionScheduler.getCurrentRegion()
.getData()
.getRegionSchedulingHandle()
.getTickReport5s(System.nanoTime());

double average = report.tpsData().segmentAll().average();
.getTickReport15s(System.nanoTime());

return report == null ? 0D : average;
return report.tpsData().segmentAll().average();
}
}
34 changes: 16 additions & 18 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,22 @@ tab:
'*': '<red>%value%</red>'

sortings:
'1':
- type: 'index'
placeholder: '<simplyrank_primary_rank>'
values:
- Owner
- Admin
- Mod
- JrMod
- Creator
- OG
- Donator
- DonatorNitro
- DonatorVoter
- default
- Designer
'2':
- type: 'alphabetical'
placeholder: '<player_displayname>'
- type: 'index'
placeholder: '<simplyrank_primary_rank>'
values:
- Owner
- Admin
- Mod
- JrMod
- Creator
- OG
- Donator
- DonatorNitro
- DonatorVoter
- default
- Designer
- type: 'alphabetical'
placeholder: '<player_displayname>'

group-prefix: '<simplyrank_prefix> <green>'
group-suffix: ''
Expand Down

0 comments on commit 764367e

Please sign in to comment.