Created
February 26, 2023 20:47
-
-
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
This file contains hidden or 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
| 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