package me.illusion.utilities; import net.minecraft.world.entity.EntityInsentient; import net.minecraft.world.entity.EntityLiving; import net.minecraft.world.entity.ai.goal.PathfinderGoal; import net.minecraft.world.entity.ai.goal.PathfinderGoalWrapped; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_18_R1.entity.CraftLivingEntity; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.entity.LivingEntity; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.world.EntitiesLoadEvent; import org.bukkit.plugin.java.JavaPlugin; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; /** * This class is used to modify the Pathfinders of any monster that may spawn, * remove or add new pathfinders, and overall provides a cleaner solution. * * For future maintainers: There are 2 PathfinderGoalSelectors present in the * EntityInsentient class, one for the pathfinding and one for the target. * * A PathfinderGoalSelector contains a List which stores * both the pathfinder goal and its priority on the list. If a PathfinderGoal * is triggered, further elements on the list won't be triggered. * * @author Illusion */ public class PathfinderInjector implements Listener { private static boolean initialized = false; private static final EnumSet wipableEntityTypes = EnumSet.noneOf(EntityType.class); private static final Map, Consumer> onSpawn = new HashMap<>(); public static void init(JavaPlugin plugin) { if (initialized) { return; } initialized = true; Bukkit.getPluginManager().registerEvents(new PathfinderInjector(), plugin); } public static void inject(Class entityClass, Function injector) { onSpawn.put( entityClass, onSpawn.getOrDefault(entityClass, (e) -> {}) .andThen((entityInsentient) -> entityInsentient .bR // Attack goal selector .c() // List of WrappedPathfinderGoal .add(new PathfinderGoalWrapped(entityInsentient.bR.c().size() + 1 /* Last priority, preserves order */, injector.apply(entityInsentient))) ) ); } public static void onSpawn(Class entityClass, Consumer onSpawn) { PathfinderInjector.onSpawn.put(entityClass, onSpawn); } public static void wipeOnSpawn(EntityType type) { wipableEntityTypes.add(type); } @EventHandler private void onSpawn(CreatureSpawnEvent event) { LivingEntity entity = event.getEntity(); EntityLiving nmsEntity = ((CraftLivingEntity) entity).getHandle(); EntityInsentient entityInsentient = (EntityInsentient) nmsEntity; if (wipableEntityTypes.contains(entity.getType())) { entityInsentient.bR.c().clear(); // attack pathfinder selector entityInsentient.bS.c().clear(); // movement pathfinder selector } onSpawn.getOrDefault(entityInsentient.getClass(), (e) -> {}).accept(entityInsentient); } @EventHandler private void onLoad(EntitiesLoadEvent event) { for (Entity entity : event.getEntities()) { if(!(entity instanceof LivingEntity)) { continue; } EntityLiving nmsEntity = ((CraftLivingEntity) entity).getHandle(); EntityInsentient entityInsentient = (EntityInsentient) nmsEntity; if (wipableEntityTypes.contains(entity.getType())) { entityInsentient.bR.c().clear(); // attack pathfinder selector entityInsentient.bS.c().clear(); // movement pathfinder selector } onSpawn.getOrDefault(entityInsentient.getClass(), (e) -> {}).accept(entityInsentient); } } }