From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: MiniDigger Date: Fri, 3 Jan 2020 16:26:19 +0100 Subject: [PATCH] Implement Mob Goal API diff --git a/build.gradle.kts b/build.gradle.kts index cb100e337521fd278893ec775606f128717105f7..d253682a020cc5cb41c9fdae48adf5c85258be62 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") testImplementation("org.hamcrest:hamcrest:2.2") diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..0a8dcfd9bdb56e988fee6404c4663aca7c5c7e98 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java @@ -0,0 +1,243 @@ +package com.destroystokyo.paper.entity.ai; + +import com.destroystokyo.paper.entity.RangedEntity; +import com.google.common.base.CaseFormat; +import java.lang.reflect.Constructor; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import io.papermc.paper.entity.SchoolableFish; +import io.papermc.paper.util.ObfHelper; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.monster.RangedAttackMob; +import org.apache.commons.lang3.math.NumberUtils; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.*; + +public class MobGoalHelper { + + private static final Map, Class> entityClassCache = new HashMap<>(); + private static final Map, Class> bukkitMap = new HashMap<>(); + + static { + // + bukkitMap.put(net.minecraft.world.entity.Mob.class, Mob.class); + bukkitMap.put(net.minecraft.world.entity.AgeableMob.class, Ageable.class); + bukkitMap.put(net.minecraft.world.entity.ambient.AmbientCreature.class, Ambient.class); + bukkitMap.put(net.minecraft.world.entity.animal.Animal.class, Animals.class); + bukkitMap.put(net.minecraft.world.entity.ambient.Bat.class, Bat.class); + bukkitMap.put(net.minecraft.world.entity.animal.Bee.class, Bee.class); + bukkitMap.put(net.minecraft.world.entity.monster.Blaze.class, Blaze.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cat.class, Cat.class); + bukkitMap.put(net.minecraft.world.entity.monster.CaveSpider.class, CaveSpider.class); + bukkitMap.put(net.minecraft.world.entity.animal.Chicken.class, Chicken.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cod.class, Cod.class); + bukkitMap.put(net.minecraft.world.entity.animal.Cow.class, Cow.class); + bukkitMap.put(net.minecraft.world.entity.PathfinderMob.class, Creature.class); + bukkitMap.put(net.minecraft.world.entity.monster.Creeper.class, Creeper.class); + bukkitMap.put(net.minecraft.world.entity.animal.Dolphin.class, Dolphin.class); + bukkitMap.put(net.minecraft.world.entity.monster.Drowned.class, Drowned.class); + bukkitMap.put(net.minecraft.world.entity.boss.enderdragon.EnderDragon.class, EnderDragon.class); + bukkitMap.put(net.minecraft.world.entity.monster.EnderMan.class, Enderman.class); + bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class); + bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class); + bukkitMap.put(net.minecraft.world.entity.animal.AbstractFish.class, Fish.class); + bukkitMap.put(net.minecraft.world.entity.animal.AbstractSchoolingFish.class, SchoolableFish.class); + bukkitMap.put(net.minecraft.world.entity.FlyingMob.class, Flying.class); + bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class); + bukkitMap.put(net.minecraft.world.entity.monster.Giant.class, Giant.class); + bukkitMap.put(net.minecraft.world.entity.animal.AbstractGolem.class, Golem.class); + bukkitMap.put(net.minecraft.world.entity.monster.Guardian.class, Guardian.class); + bukkitMap.put(net.minecraft.world.entity.monster.ElderGuardian.class, ElderGuardian.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Horse.class, Horse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractHorse.class, AbstractHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.AbstractChestedHorse.class, ChestedHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Donkey.class, Donkey.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Mule.class, Mule.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.SkeletonHorse.class, SkeletonHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.ZombieHorse.class, ZombieHorse.class); + bukkitMap.put(net.minecraft.world.entity.animal.camel.Camel.class, org.bukkit.entity.Camel.class); + bukkitMap.put(net.minecraft.world.entity.monster.AbstractIllager.class, Illager.class); + bukkitMap.put(net.minecraft.world.entity.monster.Illusioner.class, Illusioner.class); + bukkitMap.put(net.minecraft.world.entity.monster.SpellcasterIllager.class, Spellcaster.class); + bukkitMap.put(net.minecraft.world.entity.animal.IronGolem.class, IronGolem.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.Llama.class, Llama.class); + bukkitMap.put(net.minecraft.world.entity.animal.horse.TraderLlama.class, TraderLlama.class); + bukkitMap.put(net.minecraft.world.entity.monster.MagmaCube.class, MagmaCube.class); + bukkitMap.put(net.minecraft.world.entity.monster.Monster.class, Monster.class); + bukkitMap.put(net.minecraft.world.entity.monster.PatrollingMonster.class, Raider.class); // close enough + bukkitMap.put(net.minecraft.world.entity.animal.MushroomCow.class, MushroomCow.class); + bukkitMap.put(net.minecraft.world.entity.animal.Ocelot.class, Ocelot.class); + bukkitMap.put(net.minecraft.world.entity.animal.Panda.class, Panda.class); + bukkitMap.put(net.minecraft.world.entity.animal.Parrot.class, Parrot.class); + bukkitMap.put(net.minecraft.world.entity.animal.ShoulderRidingEntity.class, Parrot.class); // close enough + bukkitMap.put(net.minecraft.world.entity.monster.Phantom.class, Phantom.class); + bukkitMap.put(net.minecraft.world.entity.animal.Pig.class, Pig.class); + bukkitMap.put(net.minecraft.world.entity.monster.ZombifiedPiglin.class, PigZombie.class); + bukkitMap.put(net.minecraft.world.entity.monster.Pillager.class, Pillager.class); + bukkitMap.put(net.minecraft.world.entity.animal.PolarBear.class, PolarBear.class); + bukkitMap.put(net.minecraft.world.entity.animal.Pufferfish.class, PufferFish.class); + bukkitMap.put(net.minecraft.world.entity.animal.Rabbit.class, Rabbit.class); + bukkitMap.put(net.minecraft.world.entity.raid.Raider.class, Raider.class); + bukkitMap.put(net.minecraft.world.entity.monster.Ravager.class, Ravager.class); + bukkitMap.put(net.minecraft.world.entity.animal.Salmon.class, Salmon.class); + bukkitMap.put(net.minecraft.world.entity.animal.Sheep.class, Sheep.class); + bukkitMap.put(net.minecraft.world.entity.monster.Shulker.class, Shulker.class); + bukkitMap.put(net.minecraft.world.entity.monster.Silverfish.class, Silverfish.class); + bukkitMap.put(net.minecraft.world.entity.monster.Skeleton.class, Skeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.AbstractSkeleton.class, AbstractSkeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.Stray.class, Stray.class); + bukkitMap.put(net.minecraft.world.entity.monster.WitherSkeleton.class, WitherSkeleton.class); + bukkitMap.put(net.minecraft.world.entity.monster.Slime.class, Slime.class); + bukkitMap.put(net.minecraft.world.entity.animal.SnowGolem.class, Snowman.class); + bukkitMap.put(net.minecraft.world.entity.monster.Spider.class, Spider.class); + bukkitMap.put(net.minecraft.world.entity.animal.Squid.class, Squid.class); + bukkitMap.put(net.minecraft.world.entity.TamableAnimal.class, Tameable.class); + bukkitMap.put(net.minecraft.world.entity.animal.TropicalFish.class, TropicalFish.class); + bukkitMap.put(net.minecraft.world.entity.animal.Turtle.class, Turtle.class); + bukkitMap.put(net.minecraft.world.entity.monster.Vex.class, Vex.class); + bukkitMap.put(net.minecraft.world.entity.npc.Villager.class, Villager.class); + bukkitMap.put(net.minecraft.world.entity.npc.AbstractVillager.class, AbstractVillager.class); + bukkitMap.put(net.minecraft.world.entity.npc.WanderingTrader.class, WanderingTrader.class); + bukkitMap.put(net.minecraft.world.entity.monster.Vindicator.class, Vindicator.class); + bukkitMap.put(net.minecraft.world.entity.animal.WaterAnimal.class, WaterMob.class); + bukkitMap.put(net.minecraft.world.entity.monster.Witch.class, Witch.class); + bukkitMap.put(net.minecraft.world.entity.boss.wither.WitherBoss.class, Wither.class); + bukkitMap.put(net.minecraft.world.entity.animal.Wolf.class, Wolf.class); + bukkitMap.put(net.minecraft.world.entity.monster.Zombie.class, Zombie.class); + bukkitMap.put(net.minecraft.world.entity.monster.Husk.class, Husk.class); + bukkitMap.put(net.minecraft.world.entity.monster.ZombieVillager.class, ZombieVillager.class); + bukkitMap.put(net.minecraft.world.entity.monster.hoglin.Hoglin.class, Hoglin.class); + bukkitMap.put(net.minecraft.world.entity.monster.piglin.Piglin.class, Piglin.class); + bukkitMap.put(net.minecraft.world.entity.monster.piglin.AbstractPiglin.class, PiglinAbstract.class); + bukkitMap.put(net.minecraft.world.entity.monster.piglin.PiglinBrute.class, PiglinBrute.class); + bukkitMap.put(net.minecraft.world.entity.monster.Strider.class, Strider.class); + bukkitMap.put(net.minecraft.world.entity.monster.Zoglin.class, Zoglin.class); + bukkitMap.put(net.minecraft.world.entity.GlowSquid.class, GlowSquid.class); + bukkitMap.put(net.minecraft.world.entity.animal.axolotl.Axolotl.class, Axolotl.class); + bukkitMap.put(net.minecraft.world.entity.animal.goat.Goat.class, Goat.class); + bukkitMap.put(net.minecraft.world.entity.animal.frog.Frog.class, Frog.class); + bukkitMap.put(net.minecraft.world.entity.animal.frog.Tadpole.class, Tadpole.class); + bukkitMap.put(net.minecraft.world.entity.monster.warden.Warden.class, Warden.class); + bukkitMap.put(net.minecraft.world.entity.animal.allay.Allay.class, Allay.class); + bukkitMap.put(net.minecraft.world.entity.animal.sniffer.Sniffer.class, Sniffer.class); + bukkitMap.put(net.minecraft.world.entity.monster.breeze.Breeze.class, Breeze.class); + bukkitMap.put(net.minecraft.world.entity.animal.armadillo.Armadillo.class, Armadillo.class); + bukkitMap.put(net.minecraft.world.entity.monster.Bogged.class, Bogged.class); + bukkitMap.put(net.minecraft.world.entity.monster.creaking.Creaking.class, Creaking.class); + bukkitMap.put(net.minecraft.world.entity.monster.creaking.CreakingTransient.class, CreakingTransient.class); + bukkitMap.put(net.minecraft.world.entity.animal.AgeableWaterCreature.class, Squid.class); // close enough + // + } + + private static final Map deobfuscationMap = new HashMap<>(); + + static { + // TODO these kinda should be checked on each release, in case obfuscation changes + deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); + } + + private static String getPathName(String name) { + String pathName = name.substring(name.lastIndexOf('.') + 1); + boolean needDeobfMap = false; + + // inner classes + int firstInnerDelimiter = pathName.indexOf('$'); + if (firstInnerDelimiter != -1) { + String innerClassName = pathName.substring(firstInnerDelimiter + 1); + for (String nestedClass : innerClassName.split("\\$")) { + if (NumberUtils.isDigits(nestedClass)) { + needDeobfMap = true; + break; + } + } + if (!needDeobfMap) { + pathName = innerClassName; + } + pathName = pathName.replace('$', '_'); + // mapped, wooo! + } + + pathName = pathName.replace("TargetGoal", ""); + pathName = pathName.replace("Goal", ""); + pathName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, pathName); + + if (needDeobfMap && !deobfuscationMap.containsKey(pathName)) { + System.err.println("need to map " + name + " (" + pathName + ")"); + } + + // did we rename this key? + return deobfuscationMap.getOrDefault(pathName, pathName); + } + + public static EnumSet vanillaToPaper(Goal goal) { + EnumSet goals = EnumSet.noneOf(GoalType.class); + for (GoalType type : GoalType.values()) { + if (goal.getFlags().hasElement(paperToVanilla(type))) { + goals.add(type); + } + } + return goals; + } + + public static GoalType vanillaToPaper(Goal.Flag type) { + return switch (type) { + case MOVE -> GoalType.MOVE; + case LOOK -> GoalType.LOOK; + case JUMP -> GoalType.JUMP; + case UNKNOWN_BEHAVIOR -> GoalType.UNKNOWN_BEHAVIOR; + case TARGET -> GoalType.TARGET; + default -> throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); + }; + } + + public static EnumSet paperToVanilla(EnumSet types) { + EnumSet goals = EnumSet.noneOf(Goal.Flag.class); + for (GoalType type : types) { + goals.add(paperToVanilla(type)); + } + return goals; + } + + public static Goal.Flag paperToVanilla(GoalType type) { + return switch (type) { + case MOVE -> Goal.Flag.MOVE; + case LOOK -> Goal.Flag.LOOK; + case JUMP -> Goal.Flag.JUMP; + case UNKNOWN_BEHAVIOR -> Goal.Flag.UNKNOWN_BEHAVIOR; + case TARGET -> Goal.Flag.TARGET; + default -> throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); + }; + } + + public static GoalKey getKey(Class goalClass) { + String name = getPathName(io.papermc.paper.util.MappingEnvironment.reobf() ? ObfHelper.INSTANCE.deobfClassName(goalClass.getName()) : goalClass.getName()); + return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); + } + + private static Class getEntity(Class goalClass) { + //noinspection unchecked + return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { + for (Constructor ctor : key.getDeclaredConstructors()) { + for (Class param : ctor.getParameterTypes()) { + if (net.minecraft.world.entity.Mob.class.isAssignableFrom(param)) { + //noinspection unchecked + Class bukkitClass = toBukkitClass((Class) param); + if (bukkitClass == null) { + throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + param); // maybe just return Mob? + } + return bukkitClass; + } else if (RangedAttackMob.class.isAssignableFrom(param)) { + return RangedEntity.class; + } + } + } + throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return Mob? + }); + } + + public static Class toBukkitClass(Class nmsClass) { + return bukkitMap.get(nmsClass); + } +} diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java new file mode 100644 index 0000000000000000000000000000000000000000..26c745dd9ccdfdd5c5039f2acc5201b9b91fb274 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java @@ -0,0 +1,53 @@ +package com.destroystokyo.paper.entity.ai; + +import org.bukkit.entity.Mob; + +/** + * Wraps api in vanilla + */ +public class PaperCustomGoal extends net.minecraft.world.entity.ai.goal.Goal { + + private final Goal handle; + + public PaperCustomGoal(Goal handle) { + this.handle = handle; + + this.setFlags(MobGoalHelper.paperToVanilla(handle.getTypes())); + if (this.getFlags().size() == 0) { + this.getFlags().addUnchecked(Flag.UNKNOWN_BEHAVIOR); + } + } + + @Override + public boolean canUse() { + return handle.shouldActivate(); + } + + @Override + public boolean canContinueToUse() { + return handle.shouldStayActive(); + } + + @Override + public void start() { + handle.start(); + } + + @Override + public void stop() { + handle.stop(); + } + + @Override + public void tick() { + handle.tick(); + } + + public Goal getHandle() { + return handle; + } + + public GoalKey getKey() { + return handle.getKey(); + } +} diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java new file mode 100644 index 0000000000000000000000000000000000000000..e8a427ea777af040d0e2b9cc0ba2a80b9176d026 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java @@ -0,0 +1,226 @@ +package com.destroystokyo.paper.entity.ai; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import net.minecraft.world.entity.ai.goal.GoalSelector; +import net.minecraft.world.entity.ai.goal.WrappedGoal; +import org.bukkit.craftbukkit.entity.CraftMob; +import org.bukkit.entity.Mob; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class PaperMobGoals implements MobGoals { + + @Override + public void addGoal(T mob, int priority, Goal goal) { + CraftMob craftMob = (CraftMob) mob; + net.minecraft.world.entity.ai.goal.Goal mojangGoal; + + if (goal instanceof PaperVanillaGoal vanillaGoal) { + mojangGoal = vanillaGoal.getHandle(); + } else { + mojangGoal = new PaperCustomGoal<>(goal); + } + + getHandle(craftMob, goal.getTypes()).addGoal(priority, mojangGoal); + } + + @Override + public void removeGoal(T mob, Goal goal) { + CraftMob craftMob = (CraftMob) mob; + if (goal instanceof PaperCustomGoal) { + getHandle(craftMob, goal.getTypes()).removeGoal((net.minecraft.world.entity.ai.goal.Goal) goal); + } else if (goal instanceof PaperVanillaGoal) { + getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); + } else { + List toRemove = new LinkedList<>(); + for (WrappedGoal item : getHandle(craftMob, goal.getTypes()).getAvailableGoals()) { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { + toRemove.add(item.getGoal()); + } + } + } + + for (net.minecraft.world.entity.ai.goal.Goal g : toRemove) { + getHandle(craftMob, goal.getTypes()).removeGoal(g); + } + } + } + + @Override + public void removeAllGoals(T mob) { + for (GoalType type : GoalType.values()) { + removeAllGoals(mob, type); + } + } + + @Override + public void removeAllGoals(T mob, GoalType type) { + for (Goal goal : getAllGoals(mob, type)) { + removeGoal(mob, goal); + } + } + + @Override + public void removeGoal(T mob, GoalKey key) { + for (Goal goal : getGoals(mob, key)) { + removeGoal(mob, goal); + } + } + + @Override + public boolean hasGoal(T mob, GoalKey key) { + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + return true; + } + } + return false; + } + + @Override + public Goal getGoal(T mob, GoalKey key) { + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + return g; + } + } + return null; + } + + @Override + public Collection> getGoals(T mob, GoalKey key) { + Set> goals = new HashSet<>(); + for (Goal g : getAllGoals(mob)) { + if (g.getKey().equals(key)) { + goals.add(g); + } + } + return goals; + } + + @Override + public Collection> getAllGoals(T mob) { + Set> goals = new HashSet<>(); + for (GoalType type : GoalType.values()) { + goals.addAll(getAllGoals(mob, type)); + } + return goals; + } + + @Override + public Collection> getAllGoals(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (WrappedGoal item : getHandle(craftMob, type).getAvailableGoals()) { + if (!item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { + continue; + } + + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + } + return goals; + } + + @Override + public Collection> getAllGoalsWithout(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (GoalType internalType : GoalType.values()) { + if (internalType == type) { + continue; + } + for (WrappedGoal item : getHandle(craftMob, internalType).getAvailableGoals()) { + if (item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) { + continue; + } + + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + } + } + return goals; + } + + @Override + public Collection> getRunningGoals(T mob) { + Set> goals = new HashSet<>(); + for (GoalType type : GoalType.values()) { + goals.addAll(getRunningGoals(mob, type)); + } + return goals; + } + + @Override + public Collection> getRunningGoals(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + getHandle(craftMob, type).getAvailableGoals() + .stream().filter(WrappedGoal::isRunning) + .filter(item -> item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) + .forEach(item -> { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + }); + return goals; + } + + @Override + public Collection> getRunningGoalsWithout(T mob, GoalType type) { + CraftMob craftMob = (CraftMob) mob; + Set> goals = new HashSet<>(); + for (GoalType internalType : GoalType.values()) { + if (internalType == type) { + continue; + } + getHandle(craftMob, internalType).getAvailableGoals() + .stream() + .filter(WrappedGoal::isRunning) + .filter(item -> !item.getGoal().getFlags().hasElement(MobGoalHelper.paperToVanilla(type))) + .forEach(item -> { + if (item.getGoal() instanceof PaperCustomGoal) { + //noinspection unchecked + goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); + } else { + goals.add(item.getGoal().asPaperVanillaGoal()); + } + }); + } + return goals; + } + + private GoalSelector getHandle(CraftMob mob, EnumSet types) { + if (types.contains(GoalType.TARGET)) { + return mob.getHandle().targetSelector; + } else { + return mob.getHandle().goalSelector; + } + } + + private GoalSelector getHandle(CraftMob mob, GoalType type) { + if (type == GoalType.TARGET) { + return mob.getHandle().targetSelector; + } else { + return mob.getHandle().goalSelector; + } + } +} diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java new file mode 100644 index 0000000000000000000000000000000000000000..b5c594a5499556ad452d9939c75e150af8252e90 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java @@ -0,0 +1,61 @@ +package com.destroystokyo.paper.entity.ai; + +import java.util.EnumSet; +import net.minecraft.world.entity.ai.goal.Goal; +import org.bukkit.entity.Mob; + +/** + * Wraps vanilla in api + */ +public class PaperVanillaGoal implements VanillaGoal { + + private final Goal handle; + private final GoalKey key; + + private final EnumSet types; + + public PaperVanillaGoal(Goal handle) { + this.handle = handle; + this.key = MobGoalHelper.getKey(handle.getClass()); + this.types = MobGoalHelper.vanillaToPaper(handle); + } + + public Goal getHandle() { + return handle; + } + + @Override + public boolean shouldActivate() { + return handle.canUse(); + } + + @Override + public boolean shouldStayActive() { + return handle.canContinueToUse(); + } + + @Override + public void start() { + handle.start(); + } + + @Override + public void stop() { + handle.stop(); + } + + @Override + public void tick() { + handle.tick(); + } + + @Override + public GoalKey getKey() { + return key; + } + + @Override + public EnumSet getTypes() { + return types; + } +} diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java index a8d6d7054110b5d95830296699f004418dae10db..acc25b08ed3b9f978229fa017d23f9fa0da519e3 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/Goal.java @@ -62,7 +62,19 @@ public abstract class Goal { return (ServerLevel)world; } + // Paper start - Mob goal api + private com.destroystokyo.paper.entity.ai.PaperVanillaGoal vanillaGoal; + public com.destroystokyo.paper.entity.ai.Goal asPaperVanillaGoal() { + if(this.vanillaGoal == null) { + this.vanillaGoal = new com.destroystokyo.paper.entity.ai.PaperVanillaGoal<>(this); + } + //noinspection unchecked + return (com.destroystokyo.paper.entity.ai.Goal) this.vanillaGoal; + } + // Paper end - Mob goal api + public static enum Flag { + UNKNOWN_BEHAVIOR, // Paper - add UNKNOWN_BEHAVIOR MOVE, LOOK, JUMP, diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index e5a534c3cae53811899d33664b0a985c2442582c..4108eebafddc9c271be8c6eb5afbdca70e5fbb89 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -2966,5 +2966,11 @@ public final class CraftServer implements Server { public boolean isStopping() { return net.minecraft.server.MinecraftServer.getServer().hasStopped(); } + + private com.destroystokyo.paper.entity.ai.MobGoals mobGoals = new com.destroystokyo.paper.entity.ai.PaperMobGoals(); + @Override + public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { + return mobGoals; + } // Paper end }