2626import  org .bukkit .Location ;
2727import  org .bukkit .Material ;
2828import  org .bukkit .block .Block ;
29+ import  org .bukkit .entity .Entity ;
2930import  org .bukkit .entity .LivingEntity ;
3031import  org .bukkit .entity .Player ;
3132
33+ import  javax .annotation .Nullable ;
3234import  java .lang .invoke .MethodHandle ;
3335import  java .lang .invoke .MethodHandles ;
3436import  java .lang .invoke .MethodType ;
4951 */ 
5052public  final  class  NMSExtras  {
5153    public  static  final  Class <?> EntityLivingClass  = getNMSClass ("world.entity" , "EntityLiving" );
54+     private  static  final  MethodHandle  GET_ENTITY_HANDLE ;
5255    public  static  final  MethodHandle  EXP_PACKET ;
5356    public  static  final  MethodHandle  ENTITY_PACKET ;
5457    public  static  final  MethodHandle  WORLD_HANDLE , ENTITY_HANDLE ;
@@ -96,9 +99,11 @@ public final class NMSExtras {
9699        MethodHandle  tileEntitySign  = null , tileEntitySign_getUpdatePacket  = null , tileEntitySign_setLine  = null , signText  = null ;
97100
98101        MethodHandle  playOutMultiBlockChange  = null , multiBlockChangeInfo  = null , chunkWrapper  = null , chunkWrapperSet  = null ,
99-                 shortsOrInfo  = null , setBlockData  = null , getDataWatcher  = null , dataWatcherGetItem  = null , dataWatcherSetItem  = null ;
102+                 shortsOrInfo  = null , setBlockData  = null , getDataWatcher  = null , dataWatcherGetItem  = null ,
103+                 dataWatcherSetItem  = null , getHandle  = null ;
100104
101105        try  {
106+             Class <?> CraftEntityClass  = getCraftClass ("entity.CraftEntity" );
102107            Class <?> nmsEntityType  = getNMSClass ("world.entity" , "EntityTypes" );
103108            Class <?> nmsEntity  = getNMSClass ("world.entity" , "Entity" );
104109            Class <?> craftEntity  = getCraftClass ("entity.CraftEntity" );
@@ -115,9 +120,10 @@ public final class NMSExtras {
115120            Class <?> DataWatcherItemClass  = getNMSClass ("network.syncher" , "DataWatcher$Item" );
116121            Class <?> DataWatcherObjectClass  = getNMSClass ("network.syncher" , "DataWatcherObject" );
117122
118- //            getDataWatcher = lookup.findVirtual(EntityLivingClass, "al", MethodType.methodType(DataWatcherClass)); 
119- //            dataWatcherGetItem = lookup.findVirtual(DataWatcherClass, "b", MethodType.methodType(Object.class, DataWatcherObjectClass)); //  private <T> Item<T> c(DataWatcherObject<T> datawatcherobject) 
120- //            dataWatcherSetItem = lookup.findVirtual(DataWatcherClass, "b", MethodType.methodType(void.class, DataWatcherItemClass, Object.class)); //  private <T> Item<T> c(DataWatcherObject<T> datawatcherobject) 
123+             getHandle  = lookup .findVirtual (CraftEntityClass , "getHandle" , MethodType .methodType (nmsEntity ));
124+             getDataWatcher  = lookup .findVirtual (nmsEntity , v (20 , 2 , "al" ).v (19 , "aj" ).v (18 , "ai" ).orElse ("getDataWatcher" ), MethodType .methodType (DataWatcherClass )); // getEntityData() 
125+             dataWatcherGetItem  = lookup .findVirtual (DataWatcherClass , v (18 , "b" ).orElse ("get" ), MethodType .methodType (Object .class , DataWatcherObjectClass )); //  private <T> Item<T> c(DataWatcherObject<T> datawatcherobject) 
126+             dataWatcherSetItem  = lookup .findVirtual (DataWatcherClass , v (18 , "b" ).orElse ("set" ), MethodType .methodType (void .class , DataWatcherObjectClass , Object .class )); //  private <T> Item<T> c(DataWatcherObject<T> datawatcherobject) 
121127
122128            getBukkitEntity  = lookup .findVirtual (nmsEntity , "getBukkitEntity" , MethodType .methodType (craftEntity ));
123129            entityHandle  = lookup .findVirtual (craftEntity , "getHandle" , MethodType .methodType (nmsEntity ));
@@ -245,6 +251,7 @@ public final class NMSExtras {
245251            ex .printStackTrace ();
246252        }
247253
254+         GET_ENTITY_HANDLE  = getHandle ;
248255        GET_DATA_WATCHER  = getDataWatcher ;
249256        DATA_WATCHER_GET_ITEM  = dataWatcherGetItem ;
250257        DATA_WATCHER_SET_ITEM  = dataWatcherSetItem ;
@@ -339,66 +346,196 @@ public static void lightning(Collection<Player> players, Location location, bool
339346        }
340347    }
341348
342-     public  static  void  getData (LivingEntity   entity ,  DataWatcherItemType   id ) {
349+     public  static  Object  getData (Object   dataWatcher ,  Object   dataWatcherObject ) {
343350        try  {
344-             Object  dataWatcher  = GET_DATA_WATCHER .invoke (entity );
345-             DATA_WATCHER_GET_ITEM .invoke (dataWatcher , id );
351+             return  DATA_WATCHER_GET_ITEM .invoke (dataWatcher , dataWatcherObject );
346352        } catch  (Throwable  e ) {
347353            throw  new  RuntimeException (e );
348354        }
349355    }
350356
351-     public  static  Object  setData (LivingEntity  entity , DataWatcherItemType  id , Object  value ) {
357+     @ Nullable 
358+     public  static  Object  getEntityHandle (Entity  entity ) {
359+         Objects .requireNonNull (entity , "Cannot get handle of null entity" );
352360        try  {
353-             Object  dataWatcher  = GET_DATA_WATCHER .invoke (entity );
354-             return  DATA_WATCHER_SET_ITEM .invoke (dataWatcher , id , value );
361+             return  GET_ENTITY_HANDLE .invoke (entity );
362+         } catch  (Throwable  throwable ) {
363+             throwable .printStackTrace ();
364+             return  null ;
365+         }
366+     }
367+ 
368+     public  static  Object  getDataWatcher (Object  handle ) {
369+         try  {
370+             return  GET_DATA_WATCHER .invoke (handle );
371+         } catch  (Throwable  e ) {
372+             throw  new  RuntimeException (e );
373+         }
374+     }
375+ 
376+     public  static  Object  setData (Object  dataWatcher , Object  dataWatcherObject , Object  value ) {
377+         try  {
378+             return  DATA_WATCHER_SET_ITEM .invoke (dataWatcher , dataWatcherObject , value );
355379        } catch  (Throwable  e ) {
356380            throw  new  RuntimeException (e );
357381        }
358382    }
359383
360-     public  static  Object  getStaticField (Class <?> clazz , String  name ) {
384+     public  static  Object  getStaticFieldIgnored (Class <?> clazz , String  name ) {
385+         return  getStaticField (clazz , name , true );
386+     }
387+ 
388+     public  static  Object  getStaticField (Class <?> clazz , String  name , boolean  silent ) {
361389        try  {
362390            Field  field  = clazz .getDeclaredField (name );
363391            field .setAccessible (true );
364392            return  field .get (null );
365393        } catch  (Throwable  e ) {
366-             throw  new  RuntimeException (e );
394+             if  (!silent ) throw  new  RuntimeException (e );
395+             else  return  null ;
396+         }
397+     }
398+ 
399+     public  enum  EntityPose  {
400+         STANDING ("a" ),
401+         FALL_FLYING ("b" ),
402+         SLEEPING ("c" ),
403+         SWIMMING ("d" ),
404+         SPIN_ATTACK ("e" ),
405+         CROUCHING ("f" ),
406+         LONG_JUMPING ("g" ),
407+         DYING ("h" ),
408+         CROAKING ("i" ),
409+         USING_TONGUE ("j" ),
410+         SITTING ("k" ),
411+         ROARING ("l" ),
412+         SNIFFING ("m" ),
413+         EMERGING ("n" ),
414+         DIGGING ("o" ),
415+         ;
416+ 
417+         public  final  Object  enumValue ;
418+         private  final  boolean  supported ;
419+ 
420+         EntityPose (String  fieldName ) {
421+             boolean  supported  = true ;
422+             Object  enumValue  = null ;
423+ 
424+             try  {
425+                 Class <?> entityPose  = getNMSClass ("world.entity" , "EntityPose" );
426+                 enumValue  = entityPose .getDeclaredField (v (17 , fieldName ).orElse (name ())).get (null );
427+             } catch  (Throwable  e ) {
428+                 supported  = false ;
429+             }
430+ 
431+             this .supported  = supported ;
432+             this .enumValue  = enumValue ;
433+         }
434+ 
435+         public  boolean  isSupported () {
436+             return  supported ;
437+         }
438+ 
439+         public  Object  getEnumValue () {
440+             return  enumValue ;
367441        }
368442    }
369443
370444    public  enum  DataWatcherItemType  {
371445        // protected static final DataWatcherObject<Byte> DATA_LIVING_ENTITY_FLAGS = DataWatcher.defineId(EntityLiving.class, DataWatcherRegistry.BYTE); 
372-         DATA_LIVING_ENTITY_FLAGS (getStaticField (EntityLivingClass , "t" ));
446+         DATA_LIVING_ENTITY_FLAGS (getStaticFieldIgnored (EntityLivingClass , "t" ));
373447
374448        private  final  Object  id ;
375449
450+         private  final  boolean  supported ;
451+ 
376452        DataWatcherItemType (Object  DataWatcherObject ) {
453+             boolean  supported  = true ;
454+             Object  id  = null ;
455+ 
377456            try  {
378457                // public int a() { return this.a; } 
379458                // Method idMethod = DataWatcherObject.getClass().getMethod("a"); 
380-                 this . id  = DataWatcherObject ;
459+                 id  = DataWatcherObject ;
381460            } catch  (Throwable  e ) {
382-                 throw   new   RuntimeException ( e ) ;
461+                 supported  =  false ;
383462            }
463+ 
464+             this .supported  = supported ;
465+             this .id  = id ;
466+         }
467+ 
468+         public  boolean  isSupported () {
469+             return  supported ;
384470        }
385471
386472        public  Object  getId () {
387473            return  id ;
388474        }
389475    }
390476
391-     public  static  void  spinEntity (LivingEntity  entity , float  ticks ) {
477+     public  enum  LivingEntityFlags  {
478+         SPIN_ATTACK (0x04 );
479+ 
480+         private  final  byte  bit ;
481+ 
482+         LivingEntityFlags (int  bit ) {
483+             this .bit  = (byte ) bit ;
484+         }
485+ 
486+         public  byte  getBit () {
487+             return  bit ;
488+         }
489+     }
490+ 
491+     public  static  void  spinEntity (LivingEntity  entity , boolean  enabled ) {
492+         if  (!EntityPose .SPIN_ATTACK .isSupported ()) {
493+             throw  new  UnsupportedOperationException ("Spin attacks are not supported in "  + getVersionInformation ());
494+         }
495+ 
496+         // https://www.spigotmc.org/threads/trident-spinning-animation-riptide.426086/ 
497+         // https://www.spigotmc.org/threads/using-the-riptide-animation.469207/ 
392498        // https://wiki.vg/Entity_metadata#Living_Entity 
393499        // Referenced as "Riptiding" or "AutoSpinAttack" modes in code. 
394500        // EntityLiving.r(int ticks) doesn't exist in newer versions. 
395- //        EntityLiving entityLiv = ((CraftPlayer) entity).getHandle(); 
396- //        DataWatcher dataWatcher = entityLiv.al(); 
397- //        dataWatcher.b((DataWatcherObject<Byte>) DataWatcherItemType.DATA_LIVING_ENTITY_FLAGS.getId(), (byte) 0x04); 
501+ 
502+         // EntityLiving entityLiv = ((CraftPlayer) entity).getHandle(); 
503+         // DataWatcher dataWatcher = entityLiv.al(); 
504+         // dataWatcher.b((DataWatcherObject<Byte>) DataWatcherItemType.DATA_LIVING_ENTITY_FLAGS.getId(), (byte) 0x04); 
505+ 
506+         setLivingEntityFlag (entity , LivingEntityFlags .SPIN_ATTACK .getBit (), enabled );
507+     }
508+ 
509+     public  static  void  setLivingEntityFlag (Entity  entity , int  index , boolean  flag ) {
510+         Object  handle  = getEntityHandle (entity );
511+         Object  dataWatcher  = getDataWatcher (handle );
512+ 
513+         Object  flagItem  = DataWatcherItemType .DATA_LIVING_ENTITY_FLAGS .getId ();
514+         byte  currentFlags  = (byte ) getData (dataWatcher , flagItem );
515+         int  newFlags ;
516+ 
517+         if  (flag ) {
518+             newFlags  = currentFlags  | index ;
519+         } else  {
520+             newFlags  = currentFlags  & ~index ;
521+         }
522+ 
523+         setData (dataWatcher , flagItem , (byte ) newFlags );
524+     }
525+ 
526+     public  static  boolean  hasLivingEntityFlag (Entity  entity , int  index ) {
527+         Object  handle  = getEntityHandle (entity );
528+         Object  dataWatcher  = getDataWatcher (handle );
529+         byte  flags  = (byte ) getData (dataWatcher , DataWatcherItemType .DATA_LIVING_ENTITY_FLAGS .getId ());
530+         return  (flags  & index ) != 0 ;
531+     }
532+ 
533+     public  boolean  isAutoSpinAttack (LivingEntity  entity ) {
534+         return  hasLivingEntityFlag (entity , LivingEntityFlags .SPIN_ATTACK .getBit ());
398535    }
399536
400537    /** 
401-      * For the trident riptide animation use {@link #spinEntity(LivingEntity, float )} instead. 
538+      * For the trident riptide animation use {@link #spinEntity(LivingEntity, boolean )} instead. 
402539     */ 
403540    public  static  void  animation (Collection <? extends  Player > players , LivingEntity  entity , Animation  animation ) {
404541        try  {
0 commit comments