Skip to content

Instantly share code, notes, and snippets.

@BlvckBytes
Created February 26, 2023 20:47
Show Gist options
  • Select an option

  • Save BlvckBytes/07dc048db0be8f9e90da01588eec3578 to your computer and use it in GitHub Desktop.

Select an option

Save BlvckBytes/07dc048db0be8f9e90da01588eec3578 to your computer and use it in GitHub Desktop.
Hook into the statistics map of an EntityPlayer to receive update callbacks in real-time
import me.blvckbytes.autowirer.ICleanable;
import me.blvckbytes.autowirer.IInitializable;
import me.blvckbytes.bbreflect.IReflectionHelper;
import me.blvckbytes.bbreflect.RClass;
import me.blvckbytes.bbreflect.handle.ClassHandle;
import me.blvckbytes.bbreflect.handle.FieldHandle;
import me.blvckbytes.bbreflect.handle.predicate.Assignability;
import me.blvckbytes.bukkitboilerplate.ILogger;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2IntMap;
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.Proxy;
import java.util.HashMap;
import java.util.Map;
public class StatisticsHook implements Listener, IInitializable, ICleanable {
private final FieldHandle
F_CRAFT_PLAYER__HANDLE,
F_ENTITY_PLAYER__STATISTIC_MANGER,
F_STATISTIC_MANAGER__MAP,
F_SCOREBOARD_CRITERIA__NAME;
private final ILogger logger;
private final Map<Player, Object> vanillaStatisticsMapByPlayer;
public StatisticsHook(
IReflectionHelper reflectionHelper,
ILogger logger
) throws Exception {
this.logger = logger;
this.vanillaStatisticsMapByPlayer = new HashMap<>();
ClassHandle C_CRAFT_PLAYER = reflectionHelper.getClass(RClass.CRAFT_PLAYER);
ClassHandle C_ENTITY_PLAYER = reflectionHelper.getClass(RClass.ENTITY_PLAYER);
ClassHandle C_ENTITY = reflectionHelper.getClass(RClass.ENTITY);
ClassHandle C_STATISTIC_MANAGER = reflectionHelper.getClass(RClass.STATISTIC_MANAGER);
ClassHandle C_SCOREBOARD_CRITERIA = reflectionHelper.getClass(RClass.SCOREBOARD_CRITERIA);
F_CRAFT_PLAYER__HANDLE = C_CRAFT_PLAYER.locateField()
.withAllowSuperclass(true)
.withType(C_ENTITY, false, Assignability.TYPE_TO_TARGET)
.required();
F_ENTITY_PLAYER__STATISTIC_MANGER = C_ENTITY_PLAYER.locateField()
.withType(C_STATISTIC_MANAGER, false, Assignability.TYPE_TO_TARGET)
.required();
F_STATISTIC_MANAGER__MAP = C_STATISTIC_MANAGER.locateField()
.withType(Map.class, false, Assignability.TYPE_TO_TARGET)
.required();
F_SCOREBOARD_CRITERIA__NAME = C_SCOREBOARD_CRITERIA.locateField()
.withType(String.class)
.required();
}
@Override
public void cleanup() {
for (Player player : Bukkit.getOnlinePlayers())
detachFromStatistics(player);
}
@Override
public void initialize() {
for (Player player : Bukkit.getOnlinePlayers())
attachToStatistics(player);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
attachToStatistics(event.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
detachFromStatistics(event.getPlayer());
}
private Object getStatisticManager(Player player) throws Exception {
Object entityPlayer = F_CRAFT_PLAYER__HANDLE.get(player);
return F_ENTITY_PLAYER__STATISTIC_MANGER.get(entityPlayer);
}
private void attachToStatistics(Player player) {
try {
Object statisticManager = getStatisticManager(player);
Map<?, ?> statisticMap = (Map<?, ?>) F_STATISTIC_MANAGER__MAP.get(statisticManager);
vanillaStatisticsMapByPlayer.put(player, statisticMap);
Object proxiedMap = Proxy.newProxyInstance(
statisticManager.getClass().getClassLoader(),
new Class[]{ Object2IntMap.class },
(proxy, method, args) -> {
if (method.getName().equals("put")) {
String name = (String) F_SCOREBOARD_CRITERIA__NAME.get(args[0]);
onStatisticChange(player, name, (int) args[1]);
}
return method.invoke(statisticMap, args);
}
);
F_STATISTIC_MANAGER__MAP.set(statisticManager, proxiedMap);
} catch (Exception e) {
logger.logError(e);
}
}
private void detachFromStatistics(Player player) {
try {
Object vanillaMap = vanillaStatisticsMapByPlayer.remove(player);
if (vanillaMap == null)
return;
Object statisticManager = getStatisticManager(player);
F_STATISTIC_MANAGER__MAP.set(statisticManager, vanillaMap);
} catch (Exception e) {
logger.logError(e);
}
}
private void onStatisticChange(Player player, String statisticName, int newValue) {
System.out.println("(" + player.getName() + ") " + statisticName + " -> " + newValue);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment