Advertisement
YeetManLord

RayCastUtility code

Oct 25th, 2022
178
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 18.67 KB | Source Code | 0 0
  1. import org.bukkit.Location;
  2. import org.bukkit.Material;
  3. import org.bukkit.block.Block;
  4. import org.bukkit.block.BlockFace;
  5. import org.bukkit.entity.Entity;
  6. import org.bukkit.entity.LivingEntity;
  7. import org.bukkit.util.Vector;
  8.  
  9. import java.lang.reflect.Field;
  10. import java.util.ArrayList;
  11. import java.util.List;
  12. import java.util.stream.Collectors;
  13.  
  14. /**
  15. * Version independent ray-cast. Will ray-cast from an entities eye location with its pitch and yaw.
  16. * Will stop on hitting either an entity or a block. If it hits neither it will return an
  17. * {@link ResultType#EMPTY} with a null entity and block.
  18. * <br>
  19. * All tests were run using a 1.16.5 server with 2GB of RAM using the highest preciseness. For {@link #rayCastBlocks(LivingEntity, double, boolean, Precision)} I used
  20. * {@link Precision#PRECISE_BLOCK}, for {@link #rayCastEntities(LivingEntity, double, boolean, Precision)} I used {@link Precision#PRECISE_ENTITY} and for {@link #rayCast(LivingEntity, double, boolean, Precision)}
  21. * I used {@link Precision#PRECISE_ENTITY}
  22. * <br>
  23. * <b>TEST RESULTS:</b>
  24. * <br>
  25. * <b>NOTE:</b> Entity results will very, based on how many entities are loaded.
  26. * <ol>
  27. *     Block Ray-Cast: 80.07 ms, Precision: {@link Precision#PRECISE_BLOCK}
  28. *     <ol>
  29. *         Successfully completed 1000 ray-casts <br>
  30. *         TPS: No noticeable TPS change.
  31. *     </ol> <br>
  32. *     Entity Ray-Cast: 395.43 ms, Precision: {@link Precision#PRECISE_ENTITY}
  33. *     <ol>
  34. *         Successfully completed 1000 ray-casts <br>
  35. *          TPS: No noticeable TPS change.
  36. *     </ol> <br>
  37. *     Full Ray-Cast (Block and entity): 437.01 ms, Precision: {@link Precision#PRECISE_ENTITY}
  38. *     <ol>
  39. *            Successfully completed 1000 ray-casts <br>
  40. *            TPS: No noticeable TPS change.
  41. *     </ol> <br>
  42. *
  43. *     Block Ray-Cast: 61.64 ms, Precision: {@link Precision#ACCURATE_BLOCK}
  44. *     <ol>
  45. *         Successfully completed 1000 ray-casts <br>
  46. *         TPS: No noticeable TPS change.
  47. *     </ol> <br>
  48. *     Entity Ray-Cast: 225.14 ms, Precision: {@link Precision#ACCURATE_ENTITY}
  49. *     <ol>
  50. *         Successfully completed 1000 ray-casts <br>
  51. *          TPS: No noticeable TPS change.
  52. *     </ol> <br>
  53. *     Full Ray-Cast (Block and entity): 253.52 ms, Precision: {@link Precision#ACCURATE_ENTITY}
  54. *     <ol>
  55. *            Successfully completed 1000 ray-casts <br>
  56. *            TPS: No noticeable TPS change.
  57. *     </ol> <br>
  58. *
  59. *     Block Ray-Cast: 71.90 ms (Lol, wow), Precision: {@link Precision#SEMI_ACCURATE_BLOCK}
  60. *     <ol>
  61. *         Successfully completed 1000 ray-casts <br>
  62. *         TPS: No noticeable TPS change.
  63. *     </ol> <br>
  64. *     Entity Ray-Cast: 97.18 ms (Wow that's a lot of improvement), Precision: {@link Precision#SEMI_ACCURATE_ENTITY}
  65. *     <ol>
  66. *         Successfully completed 1000 ray-casts <br>
  67. *          TPS: No noticeable TPS change.
  68. *     </ol> <br>
  69. *     Full Ray-Cast (Block and entity): 177.97 ms, Precision: {@link Precision#SEMI_ACCURATE_ENTITY}
  70. *     <ol>
  71. *            Successfully completed 1000 ray-casts <br>
  72. *            TPS: No noticeable TPS change.
  73. *     </ol> <br>
  74. *
  75. *     Block Ray-Cast: 61.22 ms, Precision: {@link Precision#IMPRECISE_BLOCK}
  76. *     <ol>
  77. *         Successfully completed 1000 ray-casts <br>
  78. *         TPS: No noticeable TPS change.
  79. *     </ol> <br>
  80. *     Entity Ray-Cast: 71.96 ms, Precision: {@link Precision#IMPRECISE_ENTITY}
  81. *     <ol>
  82. *         Successfully completed 1000 ray-casts <br>
  83. *          TPS: No noticeable TPS change.
  84. *     </ol> <br>
  85. *     Full Ray-Cast (Block and entity): 120.24 ms, Precision: {@link Precision#IMPRECISE_ENTITY}
  86. *     <ol>
  87. *            Successfully completed 1000 ray-casts <br>
  88. *            TPS: No noticeable TPS change.
  89. *     </ol> <br>
  90. *
  91. * </ol>
  92. * <b>VERDICT:</b> You should probably use {@link Precision#ACCURATE_BLOCK} and I think you should probably use {@link Precision#PRECISE_ENTITY} or {@link Precision#ACCURATE_ENTITY}. Despite those
  93. * being resource hogs they are the only accurate way. (Sadly)
  94. */
  95. public class RayCastUtility {
  96.  
  97.     /**
  98.      * Ray-casts only blocks. Doesn't affect performance very much. Using a 1.16.5 server and calling this 1000 times, there was minimal tps issue. (Using the highest preciseness)
  99.      * This will ignore entities! If you'd like this to not happen please use
  100. {@link #rayCast(LivingEntity, double, boolean, Precision)
  101.      *
  102.      * @param entity        Entity to ray-cast from
  103.      * @param maxDistance   Maximum distance to ray-cast
  104.      * @param ignoreLiquids Whether to factor in liquids. If true, will not stop ray-casting at a liquid.
  105.      * @param precision     How many blocks (or fractions of) to advance before every next check
  106.      * @return A ray-casted block result or an empty block result
  107.      */
  108.     public static BlockRayCastResult rayCastBlocks(LivingEntity entity, double maxDistance, boolean ignoreLiquids, Precision precision) {
  109.  
  110.         Location starting = entity.getEyeLocation();
  111.         Vector direction = starting.getDirection();
  112.         Location check = starting.clone();
  113.         Location last = starting.clone();
  114.  
  115.         double distanceTraveled = 0;
  116.         while (distanceTraveled < maxDistance) {
  117.             last = check.clone();
  118.             check = getRayTraceLocation(check, direction, precision.getAdvance());
  119.             if (check.getBlock().getType() != Material.AIR && !(check.getBlock().isLiquid() && ignoreLiquids)) {
  120.                 break;
  121.             }
  122.             distanceTraveled += precision.getAdvance();
  123.         }
  124.  
  125.         Block block = check.getBlock();
  126.         BlockFace face = block.getFace(last.getBlock());
  127.  
  128.  
  129.         if (block.getType() == Material.AIR || (ignoreLiquids && block.isLiquid())) {
  130.             return new BlockRayCastResult(ResultType.EMPTY, block, face);
  131.         }
  132.         return new BlockRayCastResult(ResultType.BLOCK, block, face);
  133.     }
  134.  
  135.     /**
  136.      * Ray-casts only entities. Affect performance quite a bit. Using a 1.16.5 server and calling this 1000 time, it took quite a bit of time. (Using the highest preciseness)
  137.      *
  138.      * @param entity        Entity to ray-cast from
  139.      * @param maxDistance   Maximum distance to ray-cast
  140.      * @param ignoreLiquids Whether to factor in liquids. If true, will not stop ray-casting at a liquid.
  141.      * @param precision     How many blocks (or fractions of) to advance before every next check
  142.      * @return A ray-casted entity result or an empty entity result
  143.      */
  144.     public static EntityRayCastResult rayCastEntities(LivingEntity entity, double maxDistance, boolean ignoreLiquids, Precision precision) {
  145.         Location starting = entity.getEyeLocation();
  146.         Vector direction = starting.getDirection();
  147.         Location check = starting.clone();
  148.         BoundingBox bb = new BoundingBox(check, getRayTraceLocation(starting, direction, maxDistance + 0.5D));
  149.         List<Entity> entityList = new ArrayList<>(entity.getNearbyEntities(maxDistance + 0.5, maxDistance + 0.5, maxDistance + 0.5)).stream().filter(e -> e != entity).filter(e -> bb.isWithinBoundingBox(e.getLocation())).collect(Collectors.toList());
  150.         Entity hitResult = null;
  151.         double distanceTraveled = 0;
  152.         while (distanceTraveled < maxDistance) {
  153.             check = getRayTraceLocation(check, direction, precision.getAdvance());
  154.             if (check.getBlock().getType() != Material.AIR && !(check.getBlock().isLiquid() && ignoreLiquids)) {
  155.                 break;
  156.             }
  157.             List<Entity> results = new ArrayList<>();
  158.             for (Entity e : entityList) {
  159.                 if (e.equals(entity)) {
  160.                     continue;
  161.                 }
  162.                 try {
  163.                     Object entityBoundingBox = e.getClass().getMethod("getBoundingBox").invoke(e);
  164.                     BoundingBox entBB = new BoundingBox(entityBoundingBox);
  165.                     if (entBB.isWithinBoundingBox(check)) {
  166.                         results.add(e);
  167.                     }
  168.                 } catch (Exception ex) {
  169.                     ex.printStackTrace();
  170.                 }
  171.             }
  172.             if (results.size() > 0) {
  173.                 Entity closest = results.get(0);
  174.                 for (int i = 1; i < results.size(); i++) {
  175.                     if (results.get(i).getLocation().distance(check) < closest.getLocation().distance(check)) {
  176.                         closest = results.get(i);
  177.                     }
  178.                 }
  179.                 hitResult = closest;
  180.                 break;
  181.             }
  182.             distanceTraveled += precision.getAdvance();
  183.         }
  184.  
  185.         if (hitResult == null) {
  186.             return new EntityRayCastResult(ResultType.EMPTY, null);
  187.         }
  188.         return new EntityRayCastResult(ResultType.ENTITY, hitResult);
  189.  
  190.     }
  191.  
  192.     /**
  193.      * Ray-casts entities and blocks. Affect performance quite a bit. Using a 1.16.5 server and calling this 1000 times, it took quite a bit of time. (Using the highest preciseness)
  194.      *
  195.      * @param entity        Entity to ray-cast from
  196.      * @param maxDistance   Maximum distance to ray-cast
  197.      * @param ignoreLiquids Whether to factor in liquids. If true, will not stop ray-casting at a liquid.
  198.      * @param precision     How many blocks (or fractions of) to advance before every next check
  199.      * @return A ray-cast result or an empty result
  200.      */
  201.     public static RayCastResult rayCast(LivingEntity entity, double maxDistance, boolean ignoreLiquids, Precision precision) {
  202.         Location starting = entity.getEyeLocation();
  203.         Vector direction = starting.getDirection();
  204.         Location check = starting.clone();
  205.         Location last = starting.clone();
  206.         BoundingBox bb = new BoundingBox(check, getRayTraceLocation(starting, direction, maxDistance + 0.5D));
  207.         List<Entity> entityList = new ArrayList<>(entity.getNearbyEntities(maxDistance + 0.5, maxDistance + 0.5, maxDistance + 0.5)).stream().filter(e -> e != entity).filter(e -> bb.isWithinBoundingBox(e.getLocation())).collect(Collectors.toList());
  208.         Entity hitResult = null;
  209.         double distanceTraveled = 0;
  210.         Block blockResult = null;
  211.         while (distanceTraveled < maxDistance) {
  212.             last = check.clone();
  213.             check = getRayTraceLocation(check, direction, precision.getAdvance());
  214.             if (check.getBlock().getType() != Material.AIR && !(check.getBlock().isLiquid() && ignoreLiquids)) {
  215.                 blockResult = check.getBlock();
  216.                 break;
  217.             }
  218.             List<Entity> results = new ArrayList<>();
  219.             for (Entity e : entityList) {
  220.                 if (e.equals(entity)) {
  221.                     continue;
  222.                 }
  223.                 try {
  224.                     Object entityBoundingBox = e.getClass().getMethod("getBoundingBox").invoke(e);
  225.                     BoundingBox entBB = new BoundingBox(entityBoundingBox);
  226.                     if (entBB.isWithinBoundingBox(check)) {
  227.                         results.add(e);
  228.                     }
  229.                 } catch (Exception ex) {
  230.                     ex.printStackTrace();
  231.                 }
  232.             }
  233.             if (results.size() > 0) {
  234.                 Entity closest = results.get(0);
  235.                 for (int i = 1; i < results.size(); i++) {
  236.                     if (results.get(i).getLocation().distance(check) < closest.getLocation().distance(check)) {
  237.                         closest = results.get(i);
  238.                     }
  239.                 }
  240.                 hitResult = closest;
  241.                 break;
  242.             }
  243.             distanceTraveled += precision.getAdvance();
  244.         }
  245.  
  246.         if (hitResult == null) {
  247.             if (blockResult == null) {
  248.                 return new RayCastResult(ResultType.EMPTY, null);
  249.             } else {
  250.                 return new BlockRayCastResult(ResultType.BLOCK, blockResult, blockResult.getFace(last.getBlock()));
  251.             }
  252.         }
  253.         return new EntityRayCastResult(ResultType.ENTITY, hitResult);
  254.     }
  255.  
  256.     public static Location getRayTraceLocation(Location starting, Vector direction, double distance) {
  257.         Location ending = starting.clone().add(direction.clone().multiply(distance));
  258.         return ending;
  259.     }
  260.  
  261.     /**
  262.      * My general advice for entity ray-casting is to just use {@link #PRECISE_ENTITY}, sorry or if you're willing to have some hit or miss.
  263.      * Use {@link #ACCURATE_ENTITY}. I'm pretty sure {@link #ACCURATE_BLOCK} will work pretty well for most cases.
  264.      */
  265.     public enum Precision {
  266.  
  267.         /**
  268.          * This is kind of pointless since block ray-casts aren't very heavy, performance wise.
  269.          */
  270.         IMPRECISE_BLOCK(1D),
  271.         /**
  272.          * Definitely inaccurate but saves on performance
  273.          */
  274.         IMPRECISE_ENTITY(0.25D),
  275.         SEMI_ACCURATE_BLOCK(0.5D),
  276.         /**
  277.          * Messes up quite a bit not necessarily good for mobs with thin hitboxes.
  278.          */
  279.         SEMI_ACCURATE_ENTITY(0.125D),
  280.         /**
  281.          * Good for general purpose
  282.          */
  283.         ACCURATE_BLOCK(0.25D),
  284.         /**
  285.          * Good for general purpose, messes up a bit
  286.          */
  287.         ACCURATE_ENTITY(0.05D),
  288.         /**
  289.          * You should use this for anti-cheats if you want to be super safe.
  290.          */
  291.         PRECISE_BLOCK(0.1D),
  292.         /**
  293.          * You should use this for anti-cheats if you want to be super safe. Otherwise
  294.          */
  295.         PRECISE_ENTITY(0.01D);
  296.  
  297.         private double advance;
  298.  
  299.         Precision(double advance) {
  300.             this.advance = advance;
  301.         }
  302.  
  303.         public double getAdvance() {
  304.             return advance;
  305.         }
  306.     }
  307.  
  308.     public enum ResultType {
  309.         ENTITY, BLOCK, EMPTY
  310.     }
  311.  
  312.     public static class RayCastResult {
  313.  
  314.         private ResultType type;
  315.  
  316.         private Object object;
  317.  
  318.         public RayCastResult(ResultType type, Object object) {
  319.             this.type = type;
  320.             this.object = object;
  321.         }
  322.  
  323.         public Object get() {
  324.             return object;
  325.         }
  326.  
  327.         public ResultType getType() {
  328.             return type;
  329.         }
  330.  
  331.         public boolean isEmpty() {
  332.             return type == ResultType.EMPTY;
  333.         }
  334.  
  335.         @Override
  336.         public String toString() {
  337.             return "RayTraceResult{" +
  338.                     "type: " + type +
  339.                     ", object: " + object +
  340.                     '}';
  341.         }
  342.     }
  343.  
  344.     public static class BlockRayCastResult extends RayCastResult {
  345.  
  346.         private Block block;
  347.  
  348.         private BlockFace face;
  349.  
  350.         public BlockRayCastResult(ResultType type, Block block, BlockFace face) {
  351.             super(type, block);
  352.             this.block = block;
  353.             this.face = face;
  354.         }
  355.  
  356.         public Block getBlock() {
  357.             return block;
  358.         }
  359.  
  360.         public BlockFace getFace() {
  361.             return face;
  362.         }
  363.  
  364.         @Override
  365.         public String toString() {
  366.             return "BlockRayTraceResult{" +
  367.                     "block: " + block +
  368.                     ", face: " + face +
  369.                     '}';
  370.         }
  371.     }
  372.  
  373.     public static class EntityRayCastResult extends RayCastResult {
  374.  
  375.         private Entity entity;
  376.  
  377.         public EntityRayCastResult(ResultType type, Entity entity) {
  378.             super(type, entity);
  379.             this.entity = entity;
  380.         }
  381.  
  382.         public Entity getEntity() {
  383.             return entity;
  384.         }
  385.  
  386.         @Override
  387.         public String toString() {
  388.             return "EntityRayTraceResult{" +
  389.                     "entity: " + entity +
  390.                     '}';
  391.         }
  392.     }
  393.  
  394.     public static class BoundingBox {
  395.         private double x1;
  396.         private double y1;
  397.         private double z1;
  398.  
  399.         private double x2;
  400.         private double y2;
  401.         private double z2;
  402.  
  403.         public BoundingBox(double x1, double y1, double z1, double x2, double y2, double z2) {
  404.             this.x1 = Math.min(x1, x2);
  405.             this.y1 = Math.min(y1, y2);
  406.             this.z1 = Math.min(z1, z2);
  407.             this.x2 = Math.max(x1, x2);
  408.             this.y2 = Math.max(y1, y2);
  409.             this.z2 = Math.max(z1, z2);
  410.         }
  411.  
  412.         public BoundingBox(Object nmsObject) throws NoSuchFieldException {
  413.             try {
  414.                 this.x1 = (double) getFieldFromNmsObject("a", nmsObject);
  415.                 this.y1 = (double) getFieldFromNmsObject("b", nmsObject);
  416.                 this.z1 = (double) getFieldFromNmsObject("c", nmsObject);
  417.  
  418.                 this.x2 = (double) getFieldFromNmsObject("d", nmsObject);
  419.                 this.y2 = (double) getFieldFromNmsObject("e", nmsObject);
  420.                 this.z2 = (double) getFieldFromNmsObject("f", nmsObject);
  421.             } catch (NoSuchFieldException e) {
  422.                 this.x1 = (double) getFieldFromNmsObject("minX", nmsObject);
  423.                 this.y1 = (double) getFieldFromNmsObject("minY", nmsObject);
  424.                 this.z1 = (double) getFieldFromNmsObject("minZ", nmsObject);
  425.  
  426.                 this.x2 = (double) getFieldFromNmsObject("maxX", nmsObject);
  427.                 this.y2 = (double) getFieldFromNmsObject("maxY", nmsObject);
  428.                 this.z2 = (double) getFieldFromNmsObject("maxZ", nmsObject);
  429.             }
  430.         }
  431.  
  432.         public BoundingBox(Location loc1, Location loc2) {
  433.             this(loc1.getX(), loc1.getY(), loc1.getZ(), loc2.getX(), loc2.getY(), loc2.getZ());
  434.         }
  435.  
  436.         @Override
  437.         public String toString() {
  438.             return "BoundingBox{" +
  439.                     "x1: " + x1 +
  440.                     ", y1: " + y1 +
  441.                     ", z1: " + z1 +
  442.                     ", x2: " + x2 +
  443.                     ", y2: " + y2 +
  444.                     ", z2: " + z2 +
  445.                     '}';
  446.         }
  447.  
  448.         public boolean isWithinBoundingBox(double x, double y, double z) {
  449.             return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2 && z >= this.z1 && z <= this.z2;
  450.         }
  451.  
  452.         public boolean isWithinBoundingBox(Location location) {
  453.             return this.isWithinBoundingBox(location.getX(), location.getY(), location.getZ());
  454.         }
  455.     }
  456.  
  457.     public static Object getFieldFromNmsObject(String fieldName, Object o) throws NoSuchFieldException {
  458.  
  459.         try {
  460.             Field field;
  461.             try {
  462.                 field = o.getClass().getDeclaredField(fieldName);
  463.             } catch (NoSuchFieldException e) {
  464.                 field = o.getClass().getField(fieldName);
  465.             }
  466.             field.setAccessible(true);
  467.             Object value = field.get(o);
  468.             field.setAccessible(false);
  469.             return value;
  470.         } catch (NoSuchFieldException exc) {
  471.             throw exc;
  472.         } catch (Exception e) {
  473.             e.printStackTrace();
  474.         }
  475.  
  476.         return null;
  477.  
  478.     }
  479. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement