44import java .lang .reflect .InvocationTargetException ;
55import java .lang .reflect .Method ;
66import java .nio .channels .ClosedChannelException ;
7+ import java .util .List ;
8+ import java .util .NoSuchElementException ;
79import java .util .UUID ;
810
911import org .bukkit .Server ;
1820import dev ._2lstudios .hamsterapi .utils .Reflection ;
1921import io .netty .channel .Channel ;
2022import io .netty .channel .ChannelDuplexHandler ;
23+ import io .netty .channel .ChannelHandler ;
2124import io .netty .channel .ChannelPipeline ;
2225import io .netty .handler .codec .ByteToMessageDecoder ;
2326
@@ -270,12 +273,14 @@ public void inject() throws IllegalAccessException, InvocationTargetException, N
270273 final ByteToMessageDecoder hamsterDecoderHandler = new HamsterDecoderHandler (this );
271274 final ChannelDuplexHandler hamsterChannelHandler = new HamsterChannelHandler (this );
272275
276+ // Inject after compression
273277 if (pipeline .get ("decompress" ) != null ) {
274278 pipeline .addAfter ("decompress" , HamsterHandler .HAMSTER_DECODER , hamsterDecoderHandler );
275279 Debug .info ("Added HAMSTER_DECODER in pipeline after decompress (" + this .player .getName () + ")" );
280+ // Compression not enabled, so inject after splitter
276281 } else if (pipeline .get ("splitter" ) != null ) {
277282 pipeline .addAfter ("splitter" , HamsterHandler .HAMSTER_DECODER , hamsterDecoderHandler );
278- Debug .info ("Added HAMSTER_DECODER in pipeline after spliter (" + this .player .getName () + ")" );
283+ Debug .info ("Added HAMSTER_DECODER in pipeline after splitter (" + this .player .getName () + ")" );
279284 } else {
280285 Debug .crit ("No ChannelHandler was found on the pipeline to inject HAMSTER_DECODER ("
281286 + this .player .getName () + ")" );
@@ -297,6 +302,82 @@ public void inject() throws IllegalAccessException, InvocationTargetException, N
297302 }
298303 }
299304
305+ /**
306+ * Periodically verifies that our channel handlers are in the correct position
307+ * in the pipeline. If another plugin has injected a handler before ours,
308+ * this method will "heal" the pipeline by reordering our handlers back to
309+ * their intended, dominant position.
310+ *
311+ * This should be run a short time after the initial injection (e.g., 20 ticks later)
312+ * to ensure priority.
313+ */
314+ public void checkAndReorderHandlers () {
315+ // 1. --- Pre-flight Checks ---
316+ // Don't do anything if we were never injected or if the player is disconnected.
317+ if (!injected || channel == null || !channel .isActive ()) {
318+ return ;
319+ }
320+
321+ try {
322+ final ChannelPipeline pipeline = channel .pipeline ();
323+
324+ // 2. --- Verify and Reorder HAMSTER_DECODER ---
325+ String decoderBaseName = (pipeline .get ("decompress" ) != null ) ? "decompress" : "splitter" ;
326+ reorderHandlerIfNeeded (pipeline , HamsterHandler .HAMSTER_DECODER , decoderBaseName );
327+
328+ // 3. --- Verify and Reorder HAMSTER_CHANNEL ---
329+ String channelBaseName = "decoder" ;
330+ reorderHandlerIfNeeded (pipeline , HamsterHandler .HAMSTER_CHANNEL , channelBaseName );
331+
332+ } catch (NoSuchElementException e ) {
333+ // This can happen if a handler was removed while we were iterating. It's safe to ignore.
334+ Debug .warn ("A handler was removed from the pipeline during reordering for " + this .player .getName () + ". This is usually safe." );
335+ } catch (Exception e ) {
336+ Debug .crit ("An unexpected error occurred while reordering pipeline handlers for "
337+ + this .player .getName () + ": " + e .getMessage ());
338+ }
339+ }
340+
341+ /**
342+ * A private helper to check if a handler is correctly positioned right after its base,
343+ * and if not, removes and re-adds it.
344+ *
345+ * @param pipeline The player's channel pipeline.
346+ * @param handlerName The name of our handler to check (e.g., "hamster_decoder").
347+ * @param baseName The name of the handler it must follow (e.g., "decompress").
348+ */
349+ private void reorderHandlerIfNeeded (final ChannelPipeline pipeline , final String handlerName , final String baseName ) {
350+ // Get our handler instance and the list of current handler names.
351+ final ChannelHandler ourHandler = pipeline .get (handlerName );
352+ final List <String > names = pipeline .names ();
353+
354+ // If our handler or its base is missing, we can't do anything.
355+ if (ourHandler == null ) {
356+ Debug .warn ("Cannot reorder " + handlerName + " because it is missing from the pipeline for " + this .player .getName ());
357+ return ;
358+ }
359+ if (pipeline .get (baseName ) == null ) {
360+ Debug .warn ("Cannot reorder " + handlerName + " because its base '" + baseName + "' is missing for " + this .player .getName ());
361+ return ;
362+ }
363+
364+ // Find the positions of our handler and its intended base.
365+ final int ourHandlerIndex = names .indexOf (handlerName );
366+ final int baseHandlerIndex = names .indexOf (baseName );
367+
368+ // If our handler is not directly after the base, it's out of order.
369+ if (ourHandlerIndex != baseHandlerIndex + 1 ) {
370+ Debug .warn (handlerName + " for player " + this .player .getName ()
371+ + " is out of order. Forcing re-injection to correct position." );
372+
373+ // Re-inject the handler to its rightful place.
374+ pipeline .remove (handlerName );
375+ pipeline .addAfter (baseName , handlerName , ourHandler );
376+
377+ Debug .info (handlerName + " was successfully reordered for " + this .player .getName ());
378+ }
379+ }
380+
300381 // Injects but instead of returning an exception returns sucess (Boolean)
301382 public boolean tryInject () {
302383 try {
0 commit comments