Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push dev branch to main #8

Merged
merged 16 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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