diff --git a/Spigot-API-Patches/0202-Add-Mob-Goal-API.patch b/Spigot-API-Patches/0202-Add-Mob-Goal-API.patch new file mode 100644 index 000000000..723391ffd --- /dev/null +++ b/Spigot-API-Patches/0202-Add-Mob-Goal-API.patch @@ -0,0 +1,466 @@ +From 3837658c3b4d01d6c8ef94bc972a644eb77e222f Mon Sep 17 00:00:00 2001 +From: MiniDigger +Date: Fri, 3 Jan 2020 16:24:46 +0100 +Subject: [PATCH] Add Mob Goal API + + +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/Goal.java b/src/main/java/com/destroystokyo/paper/entity/ai/Goal.java +new file mode 100644 +index 00000000..c57c5416 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/Goal.java +@@ -0,0 +1,66 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.EnumSet; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Represents an AI goal of an entity ++ */ ++public interface Goal { ++ ++ /** ++ * Checks if this goal should be activated ++ * ++ * @return if this goal should be activated ++ */ ++ boolean shouldActivate(); ++ ++ /** ++ * Checks if this goal should stay active, defaults to {@link Goal#shouldActivate()} ++ * ++ * @return if this goal should stay active ++ */ ++ default boolean shouldStayActive() { ++ return shouldActivate(); ++ } ++ ++ /** ++ * Called when this goal gets activated ++ */ ++ default void start() { ++ } ++ ++ /** ++ * Called when this goal gets stopped ++ */ ++ default void stop() { ++ } ++ ++ /** ++ * Called each tick the goal is activated ++ */ ++ default void tick() { ++ } ++ ++ /** ++ * A unique key that identifies this type of goal. Plugins should use their own namespace, not the minecraft ++ * namespace. Additionally, this key also specifies to what mobs this goal can be applied to ++ * ++ * @return the goal key ++ */ ++ @NotNull ++ GoalKey getKey(); ++ ++ /** ++ * Returns a list of all applicable flags for this goal.
++ * ++ * This method is only called on construction. ++ * ++ * @return the subtypes. ++ */ ++ @NotNull ++ EnumSet getTypes(); ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/GoalKey.java b/src/main/java/com/destroystokyo/paper/entity/ai/GoalKey.java +new file mode 100644 +index 00000000..9cd98c6f +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/GoalKey.java +@@ -0,0 +1,64 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.google.common.base.Objects; ++ ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.StringJoiner; ++ ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.Mob; ++ ++/** ++ * ++ * Used to identify a Goal. Consists of a {@link NamespacedKey} and the type of mob the goal can be applied to ++ * ++ * @param the type of mob the goal can be applied to ++ */ ++public class GoalKey { ++ ++ private final Class entityClass; ++ private final NamespacedKey namespacedKey; ++ ++ private GoalKey(@NotNull Class entityClass, @NotNull NamespacedKey namespacedKey) { ++ this.entityClass = entityClass; ++ this.namespacedKey = namespacedKey; ++ } ++ ++ @NotNull ++ public Class getEntityClass() { ++ return entityClass; ++ } ++ ++ @NotNull ++ public NamespacedKey getNamespacedKey() { ++ return namespacedKey; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ GoalKey goalKey = (GoalKey) o; ++ return Objects.equal(entityClass, goalKey.entityClass) && ++ Objects.equal(namespacedKey, goalKey.namespacedKey); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hashCode(entityClass, namespacedKey); ++ } ++ ++ @Override ++ public String toString() { ++ return new StringJoiner(", ", GoalKey.class.getSimpleName() + "[", "]") ++ .add("entityClass=" + entityClass) ++ .add("namespacedKey=" + namespacedKey) ++ .toString(); ++ } ++ ++ @NotNull ++ public static GoalKey of(@NotNull Class entityClass, @NotNull NamespacedKey namespacedKey) { ++ return new GoalKey<>(entityClass, namespacedKey); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/GoalType.java b/src/main/java/com/destroystokyo/paper/entity/ai/GoalType.java +new file mode 100644 +index 00000000..e2b44aff +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/GoalType.java +@@ -0,0 +1,13 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++/** ++ * Represents the subtype of a goal. Used by minecraft to disable certain types of goals if needed. ++ */ ++public enum GoalType { ++ ++ MOVE, ++ LOOK, ++ JUMP, ++ TARGET ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoals.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoals.java +new file mode 100644 +index 00000000..e21f7574 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoals.java +@@ -0,0 +1,50 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.Collection; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Represents a part of the "brain" of a mob. It tracks all tasks (running or not), allows adding and removing goals ++ */ ++public interface MobGoals { ++ ++ void addGoal(@NotNull T mob, int priority, @NotNull Goal goal); ++ ++ void removeGoal(@NotNull T mob, @NotNull Goal goal); ++ ++ void removeAllGoals(@NotNull T mob); ++ ++ void removeAllGoals(@NotNull T mob, @NotNull GoalType type); ++ ++ void removeGoal(@NotNull T mob, @NotNull GoalKey key); ++ ++ boolean hasGoal(@NotNull T mob, @NotNull GoalKey key); ++ ++ @Nullable ++ Goal getGoal(@NotNull T mob, @NotNull GoalKey key); ++ ++ @NotNull ++ Collection> getGoals(@NotNull T mob, @NotNull GoalKey key); ++ ++ @NotNull ++ Collection> getAllGoals(@NotNull T mob); ++ ++ @NotNull ++ Collection> getAllGoals(@NotNull T mob, @NotNull GoalType type); ++ ++ @NotNull ++ Collection> getAllGoalsWithout(@NotNull T mob, @NotNull GoalType type); ++ ++ @NotNull ++ Collection> getRunningGoals(@NotNull T mob); ++ ++ @NotNull ++ Collection> getRunningGoals(@NotNull T mob, @NotNull GoalType type); ++ ++ @NotNull ++ Collection> getRunningGoalsWithout(@NotNull T mob, @NotNull GoalType type); ++} +diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +new file mode 100644 +index 00000000..dc60d945 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java +@@ -0,0 +1,182 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++ ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.*; ++ ++/** ++ * Represents a vanilla goal. Plugins should never implement this.
++ * Generated by VanillaPathfinderTest in paper-server ++ */ ++public interface VanillaGoal extends Goal { ++ ++ GoalKey BEE_ATTACK = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_attack")); ++ GoalKey BEE_BECOME_ANGRY = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_become_angry")); ++ GoalKey BEE_HURT_BY_OTHER = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_hurt_by_other")); ++ GoalKey BEE_WANDER = GoalKey.of(Bee.class, NamespacedKey.minecraft("bee_wander")); ++ GoalKey BLAZE_FIREBALL = GoalKey.of(Blaze.class, NamespacedKey.minecraft("blaze_fireball")); ++ GoalKey TEMPT_CHANCE = GoalKey.of(Cat.class, NamespacedKey.minecraft("tempt_chance")); ++ GoalKey CAT_AVOID_ENTITY = GoalKey.of(Cat.class, NamespacedKey.minecraft("cat_avoid_entity")); ++ GoalKey CAT_RELAX_ON_OWNER = GoalKey.of(Cat.class, NamespacedKey.minecraft("cat_relax_on_owner")); ++ GoalKey DOLPHIN_SWIM_TO_TREASURE = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_swim_to_treasure")); ++ GoalKey DOLPHIN_SWIM_WITH_PLAYER = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_swim_with_player")); ++ GoalKey DOLPHIN_PLAY_WITH_ITEMS = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("dolphin_play_with_items")); ++ GoalKey DROWNED_ATTACK = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack")); ++ GoalKey DROWNED_GOTO_BEACH = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_goto_beach")); ++ GoalKey DROWNED_GOTO_WATER = GoalKey.of(Creature.class, NamespacedKey.minecraft("drowned_goto_water")); ++ GoalKey DROWNED_SWIM_UP = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_swim_up")); ++ GoalKey DROWNED_TRIDENT_ATTACK = GoalKey.of(RangedEntity.class, NamespacedKey.minecraft("drowned_trident_attack")); ++ GoalKey ENDERMAN_PICKUP_BLOCK = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_pickup_block")); ++ GoalKey ENDERMAN_PLACE_BLOCK = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_place_block")); ++ GoalKey PLAYER_WHO_LOOKED_AT_TARGET = GoalKey.of(Enderman.class, NamespacedKey.minecraft("player_who_looked_at_target")); ++ GoalKey ENDERMAN_FREEZE_WHEN_LOOKED_AT = GoalKey.of(Enderman.class, NamespacedKey.minecraft("enderman_freeze_when_looked_at")); ++ GoalKey FISH_SWIM = GoalKey.of(Fish.class, NamespacedKey.minecraft("fish_swim")); ++ GoalKey FOX_DEFEND_TRUSTED = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_defend_trusted")); ++ GoalKey FOX_FACEPLANT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_faceplant")); ++ GoalKey FOX_BREED = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_breed")); ++ GoalKey FOX_EAT_BERRIES = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_eat_berries")); ++ GoalKey FOX_FLOAT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_float")); ++ GoalKey FOX_FOLLOW_PARENT = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_follow_parent")); ++ GoalKey FOX_LOOK_AT_PLAYER = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_look_at_player")); ++ GoalKey FOX_MELEE_ATTACK = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_melee_attack")); ++ GoalKey FOX_PANIC = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_panic")); ++ GoalKey FOX_POUNCE = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_pounce")); ++ GoalKey FOX_SEARCH_FOR_ITEMS = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_search_for_items")); ++ GoalKey FOX_STROLL_THROUGH_VILLAGE = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_stroll_through_village")); ++ GoalKey FOX_SEEK_SHELTER = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_seek_shelter")); ++ GoalKey FOX_STALK_PREY = GoalKey.of(Fox.class, NamespacedKey.minecraft("fox_stalk_prey")); ++ GoalKey GHAST_ATTACK_TARGET = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_attack_target")); ++ GoalKey GHAST_IDLE_MOVE = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_idle_move")); ++ GoalKey GHAST_MOVE_TOWARDS_TARGET = GoalKey.of(Ghast.class, NamespacedKey.minecraft("ghast_move_towards_target")); ++ GoalKey GUARDIAN_ATTACK = GoalKey.of(Guardian.class, NamespacedKey.minecraft("guardian_attack")); ++ GoalKey RAIDER_OPEN_DOOR = GoalKey.of(Illager.class, NamespacedKey.minecraft("raider_open_door")); ++ GoalKey SPELLCASTER_CAST_SPELL = GoalKey.of(Spellcaster.class, NamespacedKey.minecraft("spellcaster_cast_spell")); ++ GoalKey LLAMA_ATTACK_WOLF = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_attack_wolf")); ++ GoalKey LLAMA_HURT_BY = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_hurt_by")); ++ GoalKey LLAMATRADER_DEFENDED_WANDERING_TRADER = GoalKey.of(TraderLlama.class, NamespacedKey.minecraft("llamatrader_defended_wandering_trader")); ++ GoalKey LONG_DISTANCE_PATROL = GoalKey.of(Monster.class, NamespacedKey.minecraft("long_distance_patrol")); ++ GoalKey OCELOT_AVOID_ENTITY = GoalKey.of(Ocelot.class, NamespacedKey.minecraft("ocelot_avoid_entity")); ++ GoalKey OCELOT_TEMPT = GoalKey.of(Ocelot.class, NamespacedKey.minecraft("ocelot_tempt")); ++ GoalKey PANDA_ATTACK = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_attack")); ++ GoalKey PANDA_AVOID = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_avoid")); ++ GoalKey PANDA_BREED = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_breed")); ++ GoalKey PANDA_HURT_BY_TARGET = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_hurt_by_target")); ++ GoalKey PANDA_LIE_ON_BACK = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_lie_on_back")); ++ GoalKey PANDA_LOOK_AT_PLAYER = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_look_at_player")); ++ GoalKey PANDA_PANIC = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_panic")); ++ GoalKey PANDA_ROLL = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_roll")); ++ GoalKey PANDA_SIT = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_sit")); ++ GoalKey PANDA_SNEEZE = GoalKey.of(Panda.class, NamespacedKey.minecraft("panda_sneeze")); ++ GoalKey PHANTOM_ATTACK_PLAYER = GoalKey.of(Phantom.class, NamespacedKey.minecraft("phantom_attack_player")); ++ GoalKey PHANTOM_ATTACK_STRATEGY = GoalKey.of(Phantom.class, NamespacedKey.minecraft("phantom_attack_strategy")); ++ GoalKey ANGER = GoalKey.of(PigZombie.class, NamespacedKey.minecraft("anger")); ++ GoalKey ANGER_OTHER = GoalKey.of(PigZombie.class, NamespacedKey.minecraft("anger_other")); ++ GoalKey POLARBEAR_ATTACK_PLAYERS = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_attack_players")); ++ GoalKey POLARBEAR_HURT_BY = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_hurt_by")); ++ GoalKey POLARBEAR_MELEE = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_melee")); ++ GoalKey POLARBEAR_PANIC = GoalKey.of(PolarBear.class, NamespacedKey.minecraft("polarbear_panic")); ++ GoalKey PUFFERFISH_PUFF = GoalKey.of(PufferFish.class, NamespacedKey.minecraft("pufferfish_puff")); ++ GoalKey EAT_CARROTS = GoalKey.of(Rabbit.class, NamespacedKey.minecraft("eat_carrots")); ++ GoalKey KILLER_RABBIT_MELEE_ATTACK = GoalKey.of(Rabbit.class, NamespacedKey.minecraft("killer_rabbit_melee_attack")); ++ GoalKey RABBIT_AVOID_TARGET = GoalKey.of(Rabbit.class, NamespacedKey.minecraft("rabbit_avoid_target")); ++ GoalKey RABBIT_PANIC = GoalKey.of(Rabbit.class, NamespacedKey.minecraft("rabbit_panic")); ++ GoalKey RAIDER_HOLD_GROUND = GoalKey.of(Raider.class, NamespacedKey.minecraft("raider_hold_ground")); ++ GoalKey RAIDER_OBTAIN_BANNER = GoalKey.of(Raider.class, NamespacedKey.minecraft("raider_obtain_banner")); ++ GoalKey RAIDER_CELEBRATION = GoalKey.of(Raider.class, NamespacedKey.minecraft("raider_celebration")); ++ GoalKey RAIDER_MOVE_THROUGH_VILLAGE = GoalKey.of(Raider.class, NamespacedKey.minecraft("raider_move_through_village")); ++ GoalKey RAVAGER_MELEE_ATTACK = GoalKey.of(Ravager.class, NamespacedKey.minecraft("ravager_melee_attack")); ++ GoalKey SHULKER_ATTACK = GoalKey.of(Shulker.class, NamespacedKey.minecraft("shulker_attack")); ++ GoalKey SHULKER_DEFENSE = GoalKey.of(Shulker.class, NamespacedKey.minecraft("shulker_defense")); ++ GoalKey SHULKER_NEAREST = GoalKey.of(Shulker.class, NamespacedKey.minecraft("shulker_nearest")); ++ GoalKey SHULKER_PEEK = GoalKey.of(Shulker.class, NamespacedKey.minecraft("shulker_peek")); ++ GoalKey SILVERFISH_HIDE_IN_BLOCK = GoalKey.of(Silverfish.class, NamespacedKey.minecraft("silverfish_hide_in_block")); ++ GoalKey SILVERFISH_WAKE_OTHERS = GoalKey.of(Silverfish.class, NamespacedKey.minecraft("silverfish_wake_others")); ++ GoalKey SKELETON_MELEE = GoalKey.of(Skeleton.class, NamespacedKey.minecraft("skeleton_melee")); ++ GoalKey SLIME_IDLE = GoalKey.of(Slime.class, NamespacedKey.minecraft("slime_idle")); ++ GoalKey SLIME_NEAREST_PLAYER = GoalKey.of(Slime.class, NamespacedKey.minecraft("slime_nearest_player")); ++ GoalKey SLIME_RANDOM_DIRECTION = GoalKey.of(Slime.class, NamespacedKey.minecraft("slime_random_direction")); ++ GoalKey SLIME_RANDOM_JUMP = GoalKey.of(Slime.class, NamespacedKey.minecraft("slime_random_jump")); ++ GoalKey SPIDER_MELEE_ATTACK = GoalKey.of(Spider.class, NamespacedKey.minecraft("spider_melee_attack")); ++ GoalKey SPIDER_NEAREST_ATTACKABLE_TARGET = GoalKey.of(Spider.class, NamespacedKey.minecraft("spider_nearest_attackable_target")); ++ GoalKey SQUID = GoalKey.of(Squid.class, NamespacedKey.minecraft("squid")); ++ GoalKey SQUID_FLEE = GoalKey.of(Squid.class, NamespacedKey.minecraft("squid_flee")); ++ GoalKey TURTLE_BREED = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_breed")); ++ GoalKey TURTLE_GO_HOME = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_go_home")); ++ GoalKey TURTLE_GOTO_WATER = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_goto_water")); ++ GoalKey TURTLE_LAY_EGG = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_lay_egg")); ++ GoalKey TURTLE_PANIC = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_panic")); ++ GoalKey TURTLE_RANDOM_STROLL = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_random_stroll")); ++ GoalKey TURTLE_TEMPT = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_tempt")); ++ GoalKey TURTLE_TRAVEL = GoalKey.of(Turtle.class, NamespacedKey.minecraft("turtle_travel")); ++ GoalKey VEX_CHARGE_ATTACK = GoalKey.of(Vex.class, NamespacedKey.minecraft("vex_charge_attack")); ++ GoalKey VEX_COPY_TARGET_OF_OWNER = GoalKey.of(Vex.class, NamespacedKey.minecraft("vex_copy_target_of_owner")); ++ GoalKey VEX_RANDOM_MOVE = GoalKey.of(Vex.class, NamespacedKey.minecraft("vex_random_move")); ++ GoalKey VILLAGERTRADER_WANDER_TO_POSITION = GoalKey.of(WanderingTrader.class, NamespacedKey.minecraft("villagertrader_wander_to_position")); ++ GoalKey VINDICATOR_BREAK_DOOR = GoalKey.of(Mob.class, NamespacedKey.minecraft("vindicator_break_door")); ++ GoalKey VINDICATOR_JOHNNY_ATTACK = GoalKey.of(Vindicator.class, NamespacedKey.minecraft("vindicator_johnny_attack")); ++ GoalKey VINDICATOR_MELEE_ATTACK = GoalKey.of(Vindicator.class, NamespacedKey.minecraft("vindicator_melee_attack")); ++ GoalKey WITHER_DO_NOTHING = GoalKey.of(Wither.class, NamespacedKey.minecraft("wither_do_nothing")); ++ GoalKey WOLF_AVOID_ENTITY = GoalKey.of(Wolf.class, NamespacedKey.minecraft("wolf_avoid_entity")); ++ GoalKey ZOMBIE_ATTACK_TURTLE_EGG = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_turtle_egg")); ++ GoalKey ARROW_ATTACK = GoalKey.of(RangedEntity.class, NamespacedKey.minecraft("arrow_attack")); ++ GoalKey AVOID_TARGET = GoalKey.of(Creature.class, NamespacedKey.minecraft("avoid_target")); ++ GoalKey BEG = GoalKey.of(Wolf.class, NamespacedKey.minecraft("beg")); ++ GoalKey BOW_SHOOT = GoalKey.of(Monster.class, NamespacedKey.minecraft("bow_shoot")); ++ GoalKey BREAK_DOOR = GoalKey.of(Mob.class, NamespacedKey.minecraft("break_door")); ++ GoalKey BREATH = GoalKey.of(Creature.class, NamespacedKey.minecraft("breath")); ++ GoalKey BREED = GoalKey.of(Animals.class, NamespacedKey.minecraft("breed")); ++ GoalKey CAT_SIT_ON_BED = GoalKey.of(Cat.class, NamespacedKey.minecraft("cat_sit_on_bed")); ++ GoalKey CROSSBOW_ATTACK = GoalKey.of(Monster.class, NamespacedKey.minecraft("crossbow_attack")); ++ GoalKey DEFEND_VILLAGE = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("defend_village")); ++ GoalKey DOOR_OPEN = GoalKey.of(Mob.class, NamespacedKey.minecraft("door_open")); ++ GoalKey EAT_TILE = GoalKey.of(Mob.class, NamespacedKey.minecraft("eat_tile")); ++ GoalKey FISH_SCHOOL = GoalKey.of(Fish.class, NamespacedKey.minecraft("fish_school")); ++ GoalKey FLEE_SUN = GoalKey.of(Creature.class, NamespacedKey.minecraft("flee_sun")); ++ GoalKey FLOAT = GoalKey.of(Mob.class, NamespacedKey.minecraft("float")); ++ GoalKey FOLLOW_BOAT = GoalKey.of(Creature.class, NamespacedKey.minecraft("follow_boat")); ++ GoalKey FOLLOW_ENTITY = GoalKey.of(Mob.class, NamespacedKey.minecraft("follow_entity")); ++ GoalKey FOLLOW_OWNER = GoalKey.of(Tameable.class, NamespacedKey.minecraft("follow_owner")); ++ GoalKey FOLLOW_PARENT = GoalKey.of(Animals.class, NamespacedKey.minecraft("follow_parent")); ++ GoalKey HORSE_TRAP = GoalKey.of(SkeletonHorse.class, NamespacedKey.minecraft("horse_trap")); ++ GoalKey HURT_BY_TARGET = GoalKey.of(Creature.class, NamespacedKey.minecraft("hurt_by_target")); ++ GoalKey INTERACT = GoalKey.of(Mob.class, NamespacedKey.minecraft("interact")); ++ GoalKey JUMP_ON_BLOCK = GoalKey.of(Cat.class, NamespacedKey.minecraft("jump_on_block")); ++ GoalKey LEAP_AT_TARGET = GoalKey.of(Mob.class, NamespacedKey.minecraft("leap_at_target")); ++ GoalKey LLAMA_FOLLOW = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_follow")); ++ GoalKey LOOK_AT_PLAYER = GoalKey.of(Mob.class, NamespacedKey.minecraft("look_at_player")); ++ GoalKey LOOK_AT_TRADING_PLAYER = GoalKey.of(AbstractVillager.class, NamespacedKey.minecraft("look_at_trading_player")); ++ GoalKey MELEE_ATTACK = GoalKey.of(Creature.class, NamespacedKey.minecraft("melee_attack")); ++ GoalKey MOVE_THROUGH_VILLAGE = GoalKey.of(Creature.class, NamespacedKey.minecraft("move_through_village")); ++ GoalKey MOVE_TOWARDS_RESTRICTION = GoalKey.of(Creature.class, NamespacedKey.minecraft("move_towards_restriction")); ++ GoalKey MOVE_TOWARDS_TARGET = GoalKey.of(Creature.class, NamespacedKey.minecraft("move_towards_target")); ++ GoalKey NEAREST_ATTACKABLE_TARGET = GoalKey.of(Mob.class, NamespacedKey.minecraft("nearest_attackable_target")); ++ GoalKey NEAREST_ATTACKABLE_TARGET_WITCH = GoalKey.of(Raider.class, NamespacedKey.minecraft("nearest_attackable_target_witch")); ++ GoalKey NEAREST_HEALABLE_RAIDER = GoalKey.of(Raider.class, NamespacedKey.minecraft("nearest_healable_raider")); ++ GoalKey NEAREST_VILLAGE = GoalKey.of(Creature.class, NamespacedKey.minecraft("nearest_village")); ++ GoalKey OCELOT_ATTACK = GoalKey.of(Mob.class, NamespacedKey.minecraft("ocelot_attack")); ++ GoalKey OFFER_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("offer_flower")); ++ GoalKey OWNER_HURT_BY_TARGET = GoalKey.of(Tameable.class, NamespacedKey.minecraft("owner_hurt_by_target")); ++ GoalKey OWNER_HURT_TARGET = GoalKey.of(Tameable.class, NamespacedKey.minecraft("owner_hurt_target")); ++ GoalKey PANIC = GoalKey.of(Creature.class, NamespacedKey.minecraft("panic")); ++ GoalKey PERCH = GoalKey.of(Parrot.class, NamespacedKey.minecraft("perch")); ++ GoalKey RAID = GoalKey.of(Raider.class, NamespacedKey.minecraft("raid")); ++ GoalKey RANDOM_FLY = GoalKey.of(Creature.class, NamespacedKey.minecraft("random_fly")); ++ GoalKey RANDOM_LOOKAROUND = GoalKey.of(Mob.class, NamespacedKey.minecraft("random_lookaround")); ++ GoalKey RANDOM_STROLL = GoalKey.of(Creature.class, NamespacedKey.minecraft("random_stroll")); ++ GoalKey RANDOM_STROLL_LAND = GoalKey.of(Creature.class, NamespacedKey.minecraft("random_stroll_land")); ++ GoalKey RANDOM_SWIM = GoalKey.of(Creature.class, NamespacedKey.minecraft("random_swim")); ++ GoalKey RANDOM_TARGET_NON_TAMED = GoalKey.of(Tameable.class, NamespacedKey.minecraft("random_target_non_tamed")); ++ GoalKey REMOVE_BLOCK = GoalKey.of(Creature.class, NamespacedKey.minecraft("remove_block")); ++ GoalKey RESTRICT_SUN = GoalKey.of(Creature.class, NamespacedKey.minecraft("restrict_sun")); ++ GoalKey SIT = GoalKey.of(Tameable.class, NamespacedKey.minecraft("sit")); ++ GoalKey STROLL_VILLAGE = GoalKey.of(Creature.class, NamespacedKey.minecraft("stroll_village")); ++ GoalKey SWELL = GoalKey.of(Creeper.class, NamespacedKey.minecraft("swell")); ++ GoalKey TAME = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("tame")); ++ GoalKey TEMPT = GoalKey.of(Creature.class, NamespacedKey.minecraft("tempt")); ++ GoalKey TRADE_WITH_PLAYER = GoalKey.of(AbstractVillager.class, NamespacedKey.minecraft("trade_with_player")); ++ GoalKey USE_ITEM = GoalKey.of(Mob.class, NamespacedKey.minecraft("use_item")); ++ GoalKey WATER = GoalKey.of(Creature.class, NamespacedKey.minecraft("water")); ++ GoalKey WATER_JUMP = GoalKey.of(Dolphin.class, NamespacedKey.minecraft("water_jump")); ++ GoalKey ZOMBIE_ATTACK = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack")); ++} +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index 95ad0122..a9c10228 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -1701,6 +1701,16 @@ public final class Bukkit { + public static boolean isStopping() { + return server.isStopping(); + } ++ ++ /** ++ * Returns the {@link com.destroystokyo.paper.entity.ai.MobGoals} manager ++ * ++ * @return the mob goals manager ++ */ ++ @NotNull ++ public static com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() { ++ return server.getMobGoals(); ++ } + // Paper end + + @NotNull +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index c3fb1c27..cc06492f 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -1487,5 +1487,13 @@ public interface Server extends PluginMessageRecipient { + * @return true if server is in the process of being shutdown + */ + boolean isStopping(); ++ ++ /** ++ * Returns the {@link com.destroystokyo.paper.entity.ai.MobGoals} manager ++ * ++ * @return the mob goals manager ++ */ ++ @NotNull ++ com.destroystokyo.paper.entity.ai.MobGoals getMobGoals(); + // Paper end + } +diff --git a/src/main/java/org/bukkit/entity/Tameable.java b/src/main/java/org/bukkit/entity/Tameable.java +index 957a6016..4fecbe94 100644 +--- a/src/main/java/org/bukkit/entity/Tameable.java ++++ b/src/main/java/org/bukkit/entity/Tameable.java +@@ -3,7 +3,7 @@ package org.bukkit.entity; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +-public interface Tameable extends Entity { ++public interface Tameable extends Animals { // Paper + + /** + * Check if this is tamed +-- +2.17.1 + diff --git a/Spigot-Server-Patches/0502-Implement-Mob-Goal-API.patch b/Spigot-Server-Patches/0502-Implement-Mob-Goal-API.patch new file mode 100644 index 000000000..e899e1464 --- /dev/null +++ b/Spigot-Server-Patches/0502-Implement-Mob-Goal-API.patch @@ -0,0 +1,948 @@ +From 3ab6d7f91bbd00bbaff6cad96d959b7265111ed8 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/pom.xml b/pom.xml +index bc8438ae1..0c0051f7f 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -122,6 +122,13 @@ + 1.3 + test + ++ ++ ++ io.github.classgraph ++ classgraph ++ 4.8.47 ++ test ++ + + + +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 000000000..d6ee94107 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +@@ -0,0 +1,329 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.google.common.collect.BiMap; ++import com.google.common.collect.HashBiMap; ++ ++import com.destroystokyo.paper.entity.RangedEntity; ++import com.destroystokyo.paper.entity.ai.GoalKey; ++import com.destroystokyo.paper.entity.ai.GoalType; ++import com.destroystokyo.paper.util.set.OptimizedSmallEnumSet; ++ ++import net.minecraft.server.*; // intentional star import ++ ++import java.lang.reflect.Constructor; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++ ++import org.bukkit.NamespacedKey; ++import org.bukkit.entity.*; // intentional star import ++ ++public class MobGoalHelper { ++ ++ private static final BiMap deobfuscationMap = HashBiMap.create(); ++ private static final Map, Class> entityClassCache = new HashMap<>(); ++ private static final Map, Class> bukkitMap = new HashMap<>(); ++ ++ static final Set ignored = new HashSet<>(); ++ ++ static { ++ // TODO these kinda should be checked on each release, in case obfuscation changes ++ deobfuscationMap.put("bee_b", "bee_attack"); ++ deobfuscationMap.put("bee_c", "bee_become_angry"); ++ deobfuscationMap.put("bee_h", "bee_hurt_by_other"); ++ deobfuscationMap.put("bee_l", "bee_wander"); ++ deobfuscationMap.put("cat_a", "cat_avoid_entity"); ++ deobfuscationMap.put("cat_b", "cat_relax_on_owner"); ++ deobfuscationMap.put("dolphin_b", "dolphin_swim_to_treasure"); ++ deobfuscationMap.put("dolphin_c", "dolphin_swim_with_player"); ++ deobfuscationMap.put("dolphin_d", "dolphin_play_with_items"); ++ deobfuscationMap.put("drowned_a", "drowned_attack"); ++ deobfuscationMap.put("drowned_b", "drowned_goto_beach"); ++ deobfuscationMap.put("drowned_c", "drowned_goto_water"); ++ deobfuscationMap.put("drowned_e", "drowned_swim_up"); ++ deobfuscationMap.put("drowned_f", "drowned_trident_attack"); ++ deobfuscationMap.put("enderman_a", "enderman_freeze_when_looked_at"); ++ deobfuscationMap.put("fish_b", "fish_swim"); ++ deobfuscationMap.put("fox_a", "fox_defend_trusted"); ++ deobfuscationMap.put("fox_b", "fox_faceplant"); ++ deobfuscationMap.put("fox_e", "fox_breed"); ++ deobfuscationMap.put("fox_f", "fox_eat_berries"); ++ deobfuscationMap.put("fox_g", "fox_float"); ++ deobfuscationMap.put("fox_h", "fox_follow_parent"); ++ deobfuscationMap.put("fox_j", "fox_look_at_player"); ++ deobfuscationMap.put("fox_l", "fox_melee_attack"); ++ deobfuscationMap.put("fox_n", "fox_panic"); ++ deobfuscationMap.put("fox_o", "fox_pounce"); ++ deobfuscationMap.put("fox_p", "fox_search_for_items"); ++ deobfuscationMap.put("fox_q", "fox_stroll_through_village"); ++ deobfuscationMap.put("fox_s", "fox_seek_shelter"); ++ deobfuscationMap.put("fox_u", "fox_stalk_prey"); ++ deobfuscationMap.put("illager_abstract_b", "raider_open_door"); ++ deobfuscationMap.put("illager_wizard_b", "spellcaster_cast_spell"); ++ deobfuscationMap.put("llama_a", "llama_attack_wolf"); ++ deobfuscationMap.put("llama_c", "llama_hurt_by"); ++ deobfuscationMap.put("llama_trader_a", "llamatrader_defended_wandering_trader"); ++ deobfuscationMap.put("monster_patrolling_a", "long_distance_patrol"); ++ deobfuscationMap.put("ocelot_a", "ocelot_avoid_entity"); ++ deobfuscationMap.put("ocelot_b", "ocelot_tempt"); ++ deobfuscationMap.put("panda_b", "panda_attack"); ++ deobfuscationMap.put("panda_c", "panda_avoid"); ++ deobfuscationMap.put("panda_d", "panda_breed"); ++ deobfuscationMap.put("panda_e", "panda_hurt_by_target"); ++ deobfuscationMap.put("panda_f", "panda_lie_on_back"); ++ deobfuscationMap.put("panda_g", "panda_look_at_player"); ++ deobfuscationMap.put("panda_i", "panda_panic"); ++ deobfuscationMap.put("panda_j", "panda_roll"); ++ deobfuscationMap.put("panda_k", "panda_sit"); ++ deobfuscationMap.put("panda_l", "panda_sneeze"); ++ deobfuscationMap.put("phantom_b", "phantom_attack_player"); ++ deobfuscationMap.put("phantom_c", "phantom_attack_strategy"); ++ deobfuscationMap.put("polar_bear_a", "polarbear_attack_players"); ++ deobfuscationMap.put("polar_bear_b", "polarbear_hurt_by"); ++ deobfuscationMap.put("polar_bear_c", "polarbear_melee"); ++ deobfuscationMap.put("polar_bear_d", "polarbear_panic"); ++ deobfuscationMap.put("puffer_fish_a", "pufferfish_puff"); ++ deobfuscationMap.put("raider_a", "raider_hold_ground"); ++ deobfuscationMap.put("raider_b", "raider_obtain_banner"); ++ deobfuscationMap.put("raider_c", "raider_celebration"); ++ deobfuscationMap.put("raider_d", "raider_move_through_village"); ++ deobfuscationMap.put("ravager_a", "ravager_melee_attack"); ++ deobfuscationMap.put("shulker_a", "shulker_attack"); ++ deobfuscationMap.put("shulker_c", "shulker_defense"); ++ deobfuscationMap.put("shulker_d", "shulker_nearest"); ++ deobfuscationMap.put("shulker_e", "shulker_peek"); ++ deobfuscationMap.put("squid_a", "squid_flee"); ++ deobfuscationMap.put("skeleton_abstract_1", "skeleton_melee"); ++ deobfuscationMap.put("turtle_a", "turtle_breed"); ++ deobfuscationMap.put("turtle_b", "turtle_go_home"); ++ deobfuscationMap.put("turtle_c", "turtle_goto_water"); ++ deobfuscationMap.put("turtle_d", "turtle_lay_egg"); ++ deobfuscationMap.put("turtle_f", "turtle_panic"); ++ deobfuscationMap.put("turtle_h", "turtle_random_stroll"); ++ deobfuscationMap.put("turtle_i", "turtle_tempt"); ++ deobfuscationMap.put("turtle_j", "turtle_travel"); ++ deobfuscationMap.put("vex_a", "vex_charge_attack"); ++ deobfuscationMap.put("vex_b", "vex_copy_target_of_owner"); ++ deobfuscationMap.put("vex_d", "vex_random_move"); ++ deobfuscationMap.put("villager_trader_a", "villagertrader_wander_to_position"); ++ deobfuscationMap.put("vindicator_a", "vindicator_break_door"); ++ deobfuscationMap.put("vindicator_b", "vindicator_johnny_attack"); ++ deobfuscationMap.put("vindicator_c", "vindicator_melee_attack"); ++ deobfuscationMap.put("wither_a", "wither_do_nothing"); ++ deobfuscationMap.put("wolf_a", "wolf_avoid_entity"); ++ deobfuscationMap.put("zombie_a", "zombie_attack_turtle_egg"); ++ ++ ignored.add("selector_1"); ++ ignored.add("selector_2"); ++ ignored.add("wrapped"); ++ ++ bukkitMap.put(EntityInsentient.class, Mob.class); ++ bukkitMap.put(EntityAgeable.class, Ageable.class); ++ bukkitMap.put(EntityAmbient.class, Ambient.class); ++ bukkitMap.put(EntityAnimal.class, Animals.class); ++ bukkitMap.put(EntityBat.class, Bat.class); ++ bukkitMap.put(EntityBee.class, Bee.class); ++ bukkitMap.put(EntityBlaze.class, Blaze.class); ++ bukkitMap.put(EntityCat.class, Cat.class); ++ bukkitMap.put(EntityCaveSpider.class, CaveSpider.class); ++ bukkitMap.put(EntityChicken.class, Chicken.class); ++ bukkitMap.put(EntityCod.class, Cod.class); ++ bukkitMap.put(EntityCow.class, Cow.class); ++ bukkitMap.put(EntityCreature.class, Creature.class); ++ bukkitMap.put(EntityCreeper.class, Creeper.class); ++ bukkitMap.put(EntityDolphin.class, Dolphin.class); ++ bukkitMap.put(EntityDrowned.class, Drowned.class); ++ bukkitMap.put(EntityEnderDragon.class, EnderDragon.class); ++ bukkitMap.put(EntityEnderman.class, Enderman.class); ++ bukkitMap.put(EntityEndermite.class, Endermite.class); ++ bukkitMap.put(EntityEvoker.class, Evoker.class); ++ bukkitMap.put(EntityFish.class, Fish.class); ++ bukkitMap.put(EntityFishSchool.class, Fish.class); // close enough ++ bukkitMap.put(EntityFlying.class, Flying.class); ++ bukkitMap.put(EntityFox.class, Fox.class); ++ bukkitMap.put(EntityGhast.class, Ghast.class); ++ bukkitMap.put(EntityGiantZombie.class, Giant.class); ++ bukkitMap.put(EntityGolem.class, Golem.class); ++ bukkitMap.put(EntityGuardian.class, Guardian.class); ++ bukkitMap.put(EntityGuardianElder.class, ElderGuardian.class); ++ bukkitMap.put(EntityHorse.class, Horse.class); ++ bukkitMap.put(EntityHorseAbstract.class, AbstractHorse.class); ++ bukkitMap.put(EntityHorseChestedAbstract.class, ChestedHorse.class); ++ bukkitMap.put(EntityHorseDonkey.class, Donkey.class); ++ bukkitMap.put(EntityHorseMule.class, Mule.class); ++ bukkitMap.put(EntityHorseSkeleton.class, SkeletonHorse.class); ++ bukkitMap.put(EntityHorseZombie.class, ZombieHorse.class); ++ bukkitMap.put(EntityIllagerAbstract.class, Illager.class); ++ bukkitMap.put(EntityIllagerIllusioner.class, Illusioner.class); ++ bukkitMap.put(EntityIllagerWizard.class, Spellcaster.class); ++ bukkitMap.put(EntityIronGolem.class, IronGolem.class); ++ bukkitMap.put(EntityLlama.class, Llama.class); ++ bukkitMap.put(EntityLlamaTrader.class, TraderLlama.class); ++ bukkitMap.put(EntityMagmaCube.class, MagmaCube.class); ++ bukkitMap.put(EntityMonster.class, Monster.class); ++ bukkitMap.put(EntityMonsterPatrolling.class, Monster.class); // close enough ++ bukkitMap.put(EntityMushroomCow.class, MushroomCow.class); ++ bukkitMap.put(EntityOcelot.class, Ocelot.class); ++ bukkitMap.put(EntityPanda.class, Panda.class); ++ bukkitMap.put(EntityParrot.class, Parrot.class); ++ bukkitMap.put(EntityPerchable.class, Parrot.class); // close enough ++ bukkitMap.put(EntityPhantom.class, Phantom.class); ++ bukkitMap.put(EntityPig.class, Pig.class); ++ bukkitMap.put(EntityPigZombie.class, PigZombie.class); ++ bukkitMap.put(EntityPillager.class, Pillager.class); ++ bukkitMap.put(EntityPolarBear.class, PolarBear.class); ++ bukkitMap.put(EntityPufferFish.class, PufferFish.class); ++ bukkitMap.put(EntityRabbit.class, Rabbit.class); ++ bukkitMap.put(EntityRaider.class, Raider.class); ++ bukkitMap.put(EntityRavager.class, Ravager.class); ++ bukkitMap.put(EntitySalmon.class, Salmon.class); ++ bukkitMap.put(EntitySheep.class, Sheep.class); ++ bukkitMap.put(EntityShulker.class, Shulker.class); ++ bukkitMap.put(EntitySilverfish.class, Silverfish.class); ++ bukkitMap.put(EntitySkeleton.class, Skeleton.class); ++ bukkitMap.put(EntitySkeletonAbstract.class, Skeleton.class); ++ bukkitMap.put(EntitySkeletonStray.class, Stray.class); ++ bukkitMap.put(EntitySkeletonWither.class, WitherSkeleton.class); ++ bukkitMap.put(EntitySlime.class, Slime.class); ++ bukkitMap.put(EntitySnowman.class, Snowman.class); ++ bukkitMap.put(EntitySpider.class, Spider.class); ++ bukkitMap.put(EntitySquid.class, Squid.class); ++ bukkitMap.put(EntityTameableAnimal.class, Tameable.class); ++ bukkitMap.put(EntityTropicalFish.class, TropicalFish.class); ++ bukkitMap.put(EntityTurtle.class, Turtle.class); ++ bukkitMap.put(EntityVex.class, Vex.class); ++ bukkitMap.put(EntityVillager.class, Villager.class); ++ bukkitMap.put(EntityVillagerAbstract.class, AbstractVillager.class); ++ bukkitMap.put(EntityVillagerTrader.class, WanderingTrader.class); ++ bukkitMap.put(EntityVindicator.class, Vindicator.class); ++ bukkitMap.put(EntityWaterAnimal.class, WaterMob.class); ++ bukkitMap.put(EntityWitch.class, Witch.class); ++ bukkitMap.put(EntityWither.class, Wither.class); ++ bukkitMap.put(EntityWolf.class, Wolf.class); ++ bukkitMap.put(EntityZombie.class, Zombie.class); ++ bukkitMap.put(EntityZombieHusk.class, Husk.class); ++ bukkitMap.put(EntityZombieVillager.class, ZombieVillager.class); ++ } ++ ++ public static String getUsableName(Class clazz) { ++ String name = clazz.getName(); ++ name = name.substring(name.lastIndexOf(".") + 1); ++ boolean flag = false; ++ // inner classes ++ if (name.contains("$")) { ++ String cut = name.substring(name.indexOf("$") + 1); ++ if (cut.length() <= 2) { ++ name = name.replace("Entity", ""); ++ name = name.replace("$", "_"); ++ flag = true; ++ } else { ++ // mapped, wooo ++ name = cut; ++ } ++ } ++ name = name.replace("PathfinderGoal", ""); ++ StringBuilder sb = new StringBuilder(); ++ for (char c : name.toCharArray()) { ++ if (c >= 'A' && c <= 'Z') { ++ sb.append("_"); ++ sb.append(Character.toLowerCase(c)); ++ } else { ++ sb.append(c); ++ } ++ } ++ name = sb.toString(); ++ name = name.replaceFirst("_", ""); ++ ++ if (flag && !deobfuscationMap.containsKey(name.toLowerCase()) && !ignored.contains(name)) { ++ System.out.println("need to map " + clazz.getName() + " (" + name.toLowerCase() + ")"); ++ } ++ ++ // did we rename this key? ++ return deobfuscationMap.getOrDefault(name, name); ++ } ++ ++ public static EnumSet vanillaToPaper(OptimizedSmallEnumSet types) { ++ EnumSet goals = EnumSet.noneOf(GoalType.class); ++ for (GoalType type : GoalType.values()) { ++ if (types.hasElement(paperToVanilla(type))) { ++ goals.add(type); ++ } ++ } ++ return goals; ++ } ++ ++ public static GoalType vanillaToPaper(PathfinderGoal.Type type) { ++ switch (type) { ++ case MOVE: ++ return GoalType.MOVE; ++ case LOOK: ++ return GoalType.LOOK; ++ case JUMP: ++ return GoalType.JUMP; ++ case TARGET: ++ return GoalType.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown vanilla mob goal type " + type.name()); ++ } ++ } ++ ++ public static EnumSet paperToVanilla(EnumSet types) { ++ EnumSet goals = EnumSet.noneOf(PathfinderGoal.Type.class); ++ for (GoalType type : types) { ++ goals.add(paperToVanilla(type)); ++ } ++ return goals; ++ } ++ ++ public static PathfinderGoal.Type paperToVanilla(GoalType type) { ++ switch (type) { ++ case MOVE: ++ return PathfinderGoal.Type.MOVE; ++ case LOOK: ++ return PathfinderGoal.Type.LOOK; ++ case JUMP: ++ return PathfinderGoal.Type.JUMP; ++ case TARGET: ++ return PathfinderGoal.Type.TARGET; ++ default: ++ throw new IllegalArgumentException("Unknown paper mob goal type " + type.name()); ++ } ++ } ++ ++ public static GoalKey getKey(Class goalClass) { ++ String name = getUsableName(goalClass); ++ if (ignored.contains(name)) { ++ //noinspection unchecked ++ return (GoalKey) GoalKey.of(Mob.class, NamespacedKey.minecraft(name)); ++ } ++ return GoalKey.of(getEntity(goalClass), NamespacedKey.minecraft(name)); ++ } ++ ++ public static Class getEntity(Class goalClass) { ++ //noinspection unchecked ++ return (Class) entityClassCache.computeIfAbsent(goalClass, key -> { ++ for (Constructor ctor : key.getDeclaredConstructors()) { ++ for (int i = 0; i < ctor.getParameterCount(); i++) { ++ Class param = ctor.getParameterTypes()[i]; ++ if (EntityInsentient.class.isAssignableFrom(param)) { ++ //noinspection unchecked ++ return toBukkitClass((Class) param); ++ } else if (IRangedEntity.class.isAssignableFrom(param)) { ++ return RangedEntity.class; ++ } ++ } ++ } ++ throw new RuntimeException("Can't figure out applicable entity for mob goal " + goalClass); // maybe just return EntityInsentient? ++ }); ++ } ++ ++ public static Class toBukkitClass(Class nmsClass) { ++ Class bukkitClass = bukkitMap.get(nmsClass); ++ if (bukkitClass == null) { ++ throw new RuntimeException("Can't figure out applicable bukkit entity for nms entity " + nmsClass); // maybe just return Mob? ++ } ++ return bukkitClass; ++ } ++} +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 000000000..8e4dc2708 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperCustomGoal.java +@@ -0,0 +1,52 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.server.PathfinderGoal; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps api in vanilla ++ */ ++public class PaperCustomGoal extends PathfinderGoal { ++ ++ private final Goal handle; ++ ++ public PaperCustomGoal(Goal handle) { ++ this.handle = handle; ++ ++ this.setTypes(MobGoalHelper.paperToVanilla(handle.getTypes())); ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void onTaskReset() { ++ 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 000000000..d9df0236e +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperMobGoals.java +@@ -0,0 +1,236 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.server.PathfinderGoal; ++import net.minecraft.server.PathfinderGoalSelector; ++import net.minecraft.server.PathfinderGoalWrapped; ++ ++import java.util.Collection; ++import java.util.EnumSet; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++ ++import org.bukkit.craftbukkit.entity.CraftMob; ++import org.bukkit.entity.Mob; ++ ++public class PaperMobGoals implements MobGoals { ++ ++ private final Map> instanceCache = new HashMap<>(); ++ ++ @Override ++ public void addGoal(T mob, int priority, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ checkType(craftMob, goal.getTypes()); ++ getHandle(craftMob, goal.getTypes()).addGoal(priority, new PaperCustomGoal<>(goal)); ++ } ++ ++ @Override ++ public void removeGoal(T mob, Goal goal) { ++ CraftMob craftMob = (CraftMob) mob; ++ checkType(craftMob, goal.getTypes()); ++ if (goal instanceof PaperCustomGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal((PathfinderGoal) goal); ++ } else if (goal instanceof PaperVanillaGoal) { ++ getHandle(craftMob, goal.getTypes()).removeGoal(((PaperVanillaGoal) goal).getHandle()); ++ } else { ++ List toRemove = new LinkedList<>(); ++ for (PathfinderGoalWrapped item : getHandle(craftMob, goal.getTypes()).getTasks()) { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ if (((PaperCustomGoal) item.getGoal()).getHandle() == goal) { ++ toRemove.add(item.getGoal()); ++ } ++ } ++ } ++ ++ for (PathfinderGoal 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 (PathfinderGoalWrapped item : getHandle(craftMob, type).getTasks()) { ++ if (!item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ 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 (PathfinderGoalWrapped item : getHandle(craftMob, internalType).getTasks()) { ++ if (item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) { ++ continue; ++ } ++ ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ } ++ } ++ 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).getExecutingGoals() ++ .filter(item -> item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ 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).getExecutingGoals() ++ .filter(item -> !item.getGoal().getGoalTypes().hasElement(MobGoalHelper.paperToVanilla(type))) ++ .forEach(item -> { ++ if (item.getGoal() instanceof PaperCustomGoal) { ++ //noinspection unchecked ++ goals.add(((PaperCustomGoal) item.getGoal()).getHandle()); ++ } else { ++ //noinspection unchecked ++ goals.add((Goal) instanceCache.computeIfAbsent(item.getGoal(), PaperVanillaGoal::new)); ++ } ++ }); ++ } ++ return goals; ++ } ++ ++ private void checkType(CraftMob mob, EnumSet types) { ++ if (!hasHandle(types)) { ++ throw new IllegalArgumentException(mob + " has no goal selector for types " + types); ++ } ++ } ++ ++ private boolean hasHandle(EnumSet type) { ++ return !type.isEmpty(); ++ } ++ ++ private PathfinderGoalSelector getHandle(CraftMob mob, EnumSet types) { ++ if (types.contains(GoalType.TARGET)) { ++ return mob.getHandle().targetSelector; ++ } else { ++ return mob.getHandle().goalSelector; ++ } ++ } ++ ++ private PathfinderGoalSelector 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 000000000..263e8c65b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/entity/ai/PaperVanillaGoal.java +@@ -0,0 +1,63 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import net.minecraft.server.PathfinderGoal; ++ ++import java.util.EnumSet; ++ ++import org.bukkit.entity.Mob; ++ ++/** ++ * Wraps vanilla in api ++ */ ++public class PaperVanillaGoal implements VanillaGoal { ++ ++ private final PathfinderGoal handle; ++ private final GoalKey key; ++ ++ private final EnumSet types; ++ ++ public PaperVanillaGoal(PathfinderGoal handle) { ++ this.handle = handle; ++ this.key = MobGoalHelper.getKey(handle.getClass()); ++ this.types = MobGoalHelper.vanillaToPaper(handle.getGoalTypes()); ++ } ++ ++ public PathfinderGoal getHandle() { ++ return handle; ++ } ++ ++ @Override ++ public boolean shouldActivate() { ++ return handle.shouldActivate(); ++ } ++ ++ @Override ++ public boolean shouldStayActive() { ++ return handle.shouldStayActive(); ++ } ++ ++ @Override ++ public void start() { ++ handle.start(); ++ } ++ ++ @Override ++ public void stop() { ++ handle.onTaskReset(); ++ } ++ ++ @Override ++ public void tick() { ++ handle.tick(); ++ } ++ ++ @Override ++ public GoalKey getKey() { ++ return key; ++ } ++ ++ @Override ++ public EnumSet getTypes() { ++ return types; ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +index 9df0006c1..b3329c6fc 100644 +--- a/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java ++++ b/src/main/java/com/destroystokyo/paper/util/set/OptimizedSmallEnumSet.java +@@ -64,4 +64,8 @@ public final class OptimizedSmallEnumSet> { + public boolean hasCommonElements(final OptimizedSmallEnumSet other) { + return (other.backingSet & this.backingSet) != 0; + } ++ ++ public boolean hasElement(final E element) { ++ return (this.backingSet & (1L << element.ordinal())) != 0; ++ } + } +diff --git a/src/main/java/net/minecraft/server/PathfinderGoal.java b/src/main/java/net/minecraft/server/PathfinderGoal.java +index 93009d83f..2dfbecf39 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoal.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoal.java +@@ -10,9 +10,9 @@ public abstract class PathfinderGoal { + + public PathfinderGoal() {} + +- public abstract boolean a(); ++ public boolean a() { return this.shouldActivate(); } public boolean shouldActivate() { return false;} // Paper - OBFHELPER + +- public boolean b() { ++ public boolean b() { return this.shouldStayActive(); } public boolean shouldStayActive() { // Paper - OBFHELPER + return this.a(); + } + +@@ -20,16 +20,16 @@ public abstract class PathfinderGoal { + return true; + } + +- public void c() {} ++ public void c() { this.start(); } public void start() {} // Paper - OBFHELPER + + public void d() { + onTaskReset(); // Paper + } + public void onTaskReset() {} // Paper + +- public void e() {} ++ public void e() { this.tick(); } public void tick() {} // Paper OBFHELPER + +- public void a(EnumSet enumset) { ++ public void a(EnumSet enumset) { this.setTypes(enumset); } public void setTypes(EnumSet enumset) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + this.goalTypes.clear(); + this.goalTypes.addAllUnchecked(enumset); +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +index 84d2abbcb..a68fc11ec 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalSelector.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalSelector.java +@@ -26,7 +26,7 @@ public class PathfinderGoalSelector { + } + }; + private final Map c = new EnumMap(PathfinderGoal.Type.class); +- private final Set d = Sets.newLinkedHashSet();private Set getTasks() { return d; }// Paper - OBFHELPER ++ private final Set d = Sets.newLinkedHashSet();public Set getTasks() { return d; }// Paper - OBFHELPER + private final GameProfilerFiller e; + private final EnumSet f = EnumSet.noneOf(PathfinderGoal.Type.class); // Paper unused, but dummy to prevent plugins from crashing as hard. Theyll need to support paper in a special case if this is super important, but really doesn't seem like it would be. + private final OptimizedSmallEnumSet goalTypes = new OptimizedSmallEnumSet<>(PathfinderGoal.Type.class); // Paper - remove streams from pathfindergoalselector +@@ -37,7 +37,7 @@ public class PathfinderGoalSelector { + this.e = gameprofilerfiller; + } + +- public void a(int i, PathfinderGoal pathfindergoal) { ++ public void addGoal(int priority, PathfinderGoal goal) {a(priority, goal);} public void a(int i, PathfinderGoal pathfindergoal) { // Paper - OBFHELPER + this.d.add(new PathfinderGoalWrapped(i, pathfindergoal)); + } + +@@ -60,7 +60,7 @@ public class PathfinderGoalSelector { + } + // Paper end + +- public void a(PathfinderGoal pathfindergoal) { ++ public void removeGoal(PathfinderGoal goal) {a(goal);} public void a(PathfinderGoal pathfindergoal) { // Paper - OBFHELPER + // Paper start - remove streams from pathfindergoalselector + for (Iterator iterator = this.d.iterator(); iterator.hasNext();) { + PathfinderGoalWrapped goalWrapped = iterator.next(); +@@ -154,6 +154,7 @@ public class PathfinderGoalSelector { + this.e.exit(); + } + ++ public Stream getExecutingGoals() {return c();} // Paper - OBFHELPER + public Stream c() { + return this.d.stream().filter(PathfinderGoalWrapped::g); + } +diff --git a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +index 1b800c558..dee4e2bea 100644 +--- a/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java ++++ b/src/main/java/net/minecraft/server/PathfinderGoalWrapped.java +@@ -5,8 +5,8 @@ import javax.annotation.Nullable; + + public class PathfinderGoalWrapped extends PathfinderGoal { + +- private final PathfinderGoal a; +- private final int b; ++ private final PathfinderGoal a; public PathfinderGoal getGoal() {return a;} // Paper - OBFHELPER ++ private final int b; public int getPriority() {return b;} // Paper - OBFHELPER + private boolean c; + + public PathfinderGoalWrapped(int i, PathfinderGoal pathfindergoal) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 1647c0975..b89f99a66 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -2230,5 +2230,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 + } +diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +new file mode 100644 +index 000000000..83d34761d +--- /dev/null ++++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +@@ -0,0 +1,92 @@ ++package com.destroystokyo.paper.entity.ai; ++ ++import com.destroystokyo.paper.entity.ai.GoalKey; ++import com.destroystokyo.paper.entity.ai.MobGoalHelper; ++import com.destroystokyo.paper.entity.ai.VanillaGoal; ++ ++import net.minecraft.server.EntityInsentient; ++import net.minecraft.server.PathfinderGoal; ++ ++import org.junit.Assert; ++import org.junit.Test; ++ ++import java.lang.reflect.Field; ++import java.lang.reflect.Modifier; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Collectors; ++ ++import org.bukkit.entity.Mob; ++ ++import io.github.classgraph.ClassGraph; ++import io.github.classgraph.ScanResult; ++ ++public class VanillaMobGoalTest { ++ ++ @Test ++ public void testKeys() { ++ List> keys = new ArrayList<>(); ++ for (Field field : VanillaGoal.class.getFields()) { ++ if (field.getType().equals(GoalKey.class)) { ++ try { ++ keys.add((GoalKey) field.get(null)); ++ } catch (IllegalAccessException e) { ++ System.out.println("Skipping " + field.getName() + ": " + e.getMessage()); ++ } ++ } ++ } ++ ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft.server").scan()) { ++ classes = scanResult.getSubclasses("net.minecraft.server.PathfinderGoal").loadClasses(); ++ } ++ ++ List> vanillaNames = classes.stream() ++ .filter(clazz -> clazz.getEnclosingClass() == null || clazz.getSuperclass().getEnclosingClass() == null) ++ .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) ++ .map(goalClass -> MobGoalHelper.getKey((Class) goalClass)) ++ .collect(Collectors.toList()); ++ ++ List> missingFromAPI = new ArrayList<>(vanillaNames); ++ missingFromAPI.removeAll(keys); ++ missingFromAPI.removeIf(k -> MobGoalHelper.ignored.contains(k.getNamespacedKey().getKey())); ++ List> missingFromVanilla = new ArrayList<>(keys); ++ missingFromVanilla.removeAll(vanillaNames); ++ ++ boolean shouldFail = false; ++ if (missingFromAPI.size() != 0) { ++ System.out.println("Missing from API: "); ++ for (GoalKey key : missingFromAPI) { ++ System.out.println("GoalKey<" + key.getEntityClass().getSimpleName() + "> " + key.getNamespacedKey().getKey().toUpperCase() + ++ " = GoalKey.of(" + key.getEntityClass().getSimpleName() + ".class, NamespacedKey.minecraft(\"" + key.getNamespacedKey().getKey() + "\"));"); ++ } ++ shouldFail = true; ++ } ++ if (missingFromVanilla.size() != 0) { ++ System.out.println("Missing from vanilla: "); ++ missingFromVanilla.forEach(System.out::println); ++ shouldFail = true; ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++ ++ @Test ++ public void testBukkitMap() { ++ List> classes; ++ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft.server").scan()) { ++ classes = scanResult.getSubclasses("net.minecraft.server.EntityInsentient").loadClasses(); ++ } ++ ++ boolean shouldFail =false; ++ for (Class nmsClass : classes) { ++ Class bukkitClass = MobGoalHelper.toBukkitClass((Class) nmsClass); ++ if(bukkitClass == null) { ++ shouldFail = true; ++ System.out.println("Missing bukkitMap.put(" + nmsClass.getSimpleName() + ".class, "+nmsClass.getSimpleName().replace("Entity","")+".class);"); ++ } ++ } ++ ++ if (shouldFail) Assert.fail("See above"); ++ } ++} +-- +2.17.1 +