Preliminary API for 'new' brains AI system for mobs

Would fix #10443 if done.

This is a rough proposal for abstracting over the brains AI system for mobs
for plugins to make use of, similar to the Goals API. I don't think it's feasible
to attempt to make the new AI conform to the old interfaces.

At the moment I understand the rough skeleton and arteries of the system, though I'm
still trying to piece together what does what in user-facing terms.
'get the mob to walk somewhere' has been unsuccessful so far.

As far as API stability concerns, as long as the exposed set of tasks isn't too 1:1 with what
Mojang has, then we should be fine (until the next Big Rewrite™️ whenever that is) considering
the general stability of the system across versions since its introductions.

There are still some unanswered questions in the API design:
- do events make sense to have here? I'd say no considering the amount of lambdas in the Minecraft implementation
  that make any real introspection API unfeasible.
- do we care to expose sensors/make them modifiable? It could probably be done in a followup PR to make custom
  tasks & sensors a thing, as I'm mostly concerned about exposing what exists in Vanilla at the moment.
- is the arrangement of the entrypoints to the new API sensical?

Test plugin is included in PR to ease testing of the new API until it's ready to ship.
This commit is contained in:
Janet Blackquill 2024-04-26 23:59:25 -04:00
parent 39532a1f8c
commit ba28ca2505
6 changed files with 1116 additions and 0 deletions

View File

@ -0,0 +1,217 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.entity.ai.Activity;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Vanilla keys for {@link RegistryKey#ACTIVITY}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@GeneratedFrom("1.20.6")
@ApiStatus.Experimental
public final class ActivityKeys {
/**
* {@code minecraft:admire_item}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> ADMIRE_ITEM = create(key("admire_item"));
/**
* {@code minecraft:avoid}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> AVOID = create(key("avoid"));
/**
* {@code minecraft:celebrate}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> CELEBRATE = create(key("celebrate"));
/**
* {@code minecraft:core}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> CORE = create(key("core"));
/**
* {@code minecraft:dig}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> DIG = create(key("dig"));
/**
* {@code minecraft:emerge}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> EMERGE = create(key("emerge"));
/**
* {@code minecraft:fight}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> FIGHT = create(key("fight"));
/**
* {@code minecraft:hide}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> HIDE = create(key("hide"));
/**
* {@code minecraft:idle}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> IDLE = create(key("idle"));
/**
* {@code minecraft:investigate}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> INVESTIGATE = create(key("investigate"));
/**
* {@code minecraft:lay_spawn}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> LAY_SPAWN = create(key("lay_spawn"));
/**
* {@code minecraft:long_jump}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> LONG_JUMP = create(key("long_jump"));
/**
* {@code minecraft:meet}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> MEET = create(key("meet"));
/**
* {@code minecraft:panic}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> PANIC = create(key("panic"));
/**
* {@code minecraft:play}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> PLAY = create(key("play"));
/**
* {@code minecraft:play_dead}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> PLAY_DEAD = create(key("play_dead"));
/**
* {@code minecraft:pre_raid}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> PRE_RAID = create(key("pre_raid"));
/**
* {@code minecraft:raid}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> RAID = create(key("raid"));
/**
* {@code minecraft:ram}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> RAM = create(key("ram"));
/**
* {@code minecraft:rest}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> REST = create(key("rest"));
/**
* {@code minecraft:ride}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> RIDE = create(key("ride"));
/**
* {@code minecraft:roar}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> ROAR = create(key("roar"));
/**
* {@code minecraft:sniff}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> SNIFF = create(key("sniff"));
/**
* {@code minecraft:swim}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> SWIM = create(key("swim"));
/**
* {@code minecraft:tongue}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> TONGUE = create(key("tongue"));
/**
* {@code minecraft:work}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<Activity> WORK = create(key("work"));
private ActivityKeys() {
}
private static @NotNull TypedKey<Activity> create(final @NotNull Key key) {
return TypedKey.create(RegistryKey.ACTIVITY, key);
}
}

View File

@ -0,0 +1,217 @@
package io.papermc.paper.registry.keys;
import static net.kyori.adventure.key.Key.key;
import io.papermc.paper.entity.ai.SensorType;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import net.kyori.adventure.key.Key;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Vanilla keys for {@link RegistryKey#SENSOR_TYPE}.
*
* @apiNote The fields provided here are a direct representation of
* what is available from the vanilla game source. They may be
* changed (including removals) on any Minecraft version
* bump, so cross-version compatibility is not provided on the
* same level as it is on most of the other API.
*/
@SuppressWarnings({
"unused",
"SpellCheckingInspection"
})
@GeneratedFrom("1.20.6")
@ApiStatus.Experimental
public final class SensorTypeKeys {
/**
* {@code minecraft:armadillo_scare_detected}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> ARMADILLO_SCARE_DETECTED = create(key("armadillo_scare_detected"));
/**
* {@code minecraft:armadillo_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> ARMADILLO_TEMPTATIONS = create(key("armadillo_temptations"));
/**
* {@code minecraft:axolotl_attackables}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> AXOLOTL_ATTACKABLES = create(key("axolotl_attackables"));
/**
* {@code minecraft:axolotl_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> AXOLOTL_TEMPTATIONS = create(key("axolotl_temptations"));
/**
* {@code minecraft:breeze_attack_entity_sensor}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> BREEZE_ATTACK_ENTITY_SENSOR = create(key("breeze_attack_entity_sensor"));
/**
* {@code minecraft:camel_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> CAMEL_TEMPTATIONS = create(key("camel_temptations"));
/**
* {@code minecraft:dummy}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> DUMMY = create(key("dummy"));
/**
* {@code minecraft:frog_attackables}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> FROG_ATTACKABLES = create(key("frog_attackables"));
/**
* {@code minecraft:frog_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> FROG_TEMPTATIONS = create(key("frog_temptations"));
/**
* {@code minecraft:goat_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> GOAT_TEMPTATIONS = create(key("goat_temptations"));
/**
* {@code minecraft:golem_detected}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> GOLEM_DETECTED = create(key("golem_detected"));
/**
* {@code minecraft:hoglin_specific_sensor}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> HOGLIN_SPECIFIC_SENSOR = create(key("hoglin_specific_sensor"));
/**
* {@code minecraft:hurt_by}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> HURT_BY = create(key("hurt_by"));
/**
* {@code minecraft:is_in_water}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> IS_IN_WATER = create(key("is_in_water"));
/**
* {@code minecraft:nearest_adult}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> NEAREST_ADULT = create(key("nearest_adult"));
/**
* {@code minecraft:nearest_bed}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> NEAREST_BED = create(key("nearest_bed"));
/**
* {@code minecraft:nearest_items}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> NEAREST_ITEMS = create(key("nearest_items"));
/**
* {@code minecraft:nearest_living_entities}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> NEAREST_LIVING_ENTITIES = create(key("nearest_living_entities"));
/**
* {@code minecraft:nearest_players}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> NEAREST_PLAYERS = create(key("nearest_players"));
/**
* {@code minecraft:piglin_brute_specific_sensor}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> PIGLIN_BRUTE_SPECIFIC_SENSOR = create(key("piglin_brute_specific_sensor"));
/**
* {@code minecraft:piglin_specific_sensor}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> PIGLIN_SPECIFIC_SENSOR = create(key("piglin_specific_sensor"));
/**
* {@code minecraft:secondary_pois}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> SECONDARY_POIS = create(key("secondary_pois"));
/**
* {@code minecraft:sniffer_temptations}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> SNIFFER_TEMPTATIONS = create(key("sniffer_temptations"));
/**
* {@code minecraft:villager_babies}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> VILLAGER_BABIES = create(key("villager_babies"));
/**
* {@code minecraft:villager_hostiles}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> VILLAGER_HOSTILES = create(key("villager_hostiles"));
/**
* {@code minecraft:warden_entity_sensor}
*
* @apiNote This field is version-dependant and may be removed in future Minecraft versions
*/
public static final TypedKey<SensorType> WARDEN_ENTITY_SENSOR = create(key("warden_entity_sensor"));
private SensorTypeKeys() {
}
private static @NotNull TypedKey<SensorType> create(final @NotNull Key key) {
return TypedKey.create((RegistryKey<SensorType>)(Object)RegistryKey.SENSOR_TYPE, key);
}
}

View File

@ -17,6 +17,8 @@ import org.bukkit.generator.structure.Structure;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import io.papermc.paper.entity.ai.Activity;
import io.papermc.paper.entity.ai.SensorType;
import org.bukkit.potion.PotionEffectType;
public interface Generators {
@ -33,6 +35,8 @@ public interface Generators {
simpleKey("MobEffectKeys", PotionEffectType.class, Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, false),
simpleKey("DamageTypeKeys", DamageType.class, Registries.DAMAGE_TYPE, RegistryKey.DAMAGE_TYPE, true),
simpleKey("WolfVariantKeys", Wolf.Variant.class, Registries.WOLF_VARIANT, RegistryKey.WOLF_VARIANT, true),
simpleKey("ActivityKeys", Activity.class, Registries.ACTIVITY, RegistryKey.ACTIVITY, false),
simpleKey("SensorTypeKeys", SensorType.class, Registries.SENSOR_TYPE, (RegistryKey<SensorType>)(Object)RegistryKey.SENSOR_TYPE, false),
new MobGoalGenerator("VanillaGoal", "com.destroystokyo.paper.entity.ai")
};

View File

@ -0,0 +1,236 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janet Blackquill <uhhadd@gmail.com>
Date: Fri, 26 Apr 2024 23:45:11 -0400
Subject: [PATCH] Add preliminary API for working with 'new' mob AI system
diff --git a/src/main/java/io/papermc/paper/entity/ai/Activity.java b/src/main/java/io/papermc/paper/entity/ai/Activity.java
new file mode 100644
index 0000000000000000000000000000000000000000..0dff7ba2e36c1185453832e28466c82ccb2cd621
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/Activity.java
@@ -0,0 +1,9 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.Keyed;
+
+/**
+ * An Activity is a key to a list of tasks in a Brain.
+ */
+public interface Activity extends Keyed {
+}
diff --git a/src/main/java/io/papermc/paper/entity/ai/Brain.java b/src/main/java/io/papermc/paper/entity/ai/Brain.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1f170f12e1a3403ff6b392460cb9e2c5b1f5eeb
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/Brain.java
@@ -0,0 +1,53 @@
+package io.papermc.paper.entity.ai;
+
+import java.util.List;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Entity;
+import org.bukkit.Location;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * A brain is the central AI for some modern Minecraft entities such as
+ * villagers and sniffers.
+ */
+public interface Brain<E extends LivingEntity> {
+ /**
+ * If the tasks in the given `activity` can be run, the brain will switch to
+ * the given `activity`.
+ */
+ public void useActivityIfPossible(@NotNull Activity activity);
+ /**
+ * Makes the brain switch to the default activity.
+ */
+ public void useDefaultActivity();
+ /**
+ * Sets the sensors in use by this brain
+ */
+ public void setSensors(@NotNull List<SensorType<E>> sensors);
+ /**
+ * Sets the tasks for the given `activity` to the provided list of `tasks`.
+ */
+ ////// TODO: what is that int parameter doing
+ public void setTasksForActivity(@NotNull Activity activity, int begin, @NotNull List<Task<E>> tasks);
+ /**
+ * Clears all tasks for activities associated with this brain.
+ */
+ public void clearActivities();
+ /**
+ * Sets the default activity for this brain.
+ */
+ public void setDefaultActivity(@NotNull Activity activity);
+ /**
+ * Checks whether the given activity is active.
+ */
+ public boolean isActive(@NotNull Activity activity);
+ /**
+ * Sets the entity's current walk target to the given location.
+ */
+ public void setWalkTarget(@NotNull Location location, float speed, int completeWithinDistance);
+ /**
+ * Sets the entity's current walk target to the given entity.
+ */
+ public void setWalkTarget(@NotNull Entity entity, float speed, int completeWithinDistance);
+}
+
diff --git a/src/main/java/io/papermc/paper/entity/ai/SensorType.java b/src/main/java/io/papermc/paper/entity/ai/SensorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..49fffc2ac7a421350d87bcffb5861b474bc85abb
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/SensorType.java
@@ -0,0 +1,11 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.Keyed;
+import org.bukkit.entity.LivingEntity;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Brains run sensors periodically for expensive-to-compute memories
+ */
+public interface SensorType<E extends LivingEntity> extends Keyed {
+}
diff --git a/src/main/java/io/papermc/paper/entity/ai/Task.java b/src/main/java/io/papermc/paper/entity/ai/Task.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ee58cbb3a520a473e28e48e378c8a9fcb7d24eb
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/Task.java
@@ -0,0 +1,10 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.entity.LivingEntity;
+
+/**
+ * A task can be associated with an Activity in a Brain in order
+ * to instruct the entity to do something.
+ */
+public interface Task<E extends LivingEntity> {
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/entity/ai/Tasks.java b/src/main/java/io/papermc/paper/entity/ai/Tasks.java
new file mode 100644
index 0000000000000000000000000000000000000000..955dec447a3b93016b62b161b5018fd75251f962
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/Tasks.java
@@ -0,0 +1,45 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Mob;
+import org.bukkit.entity.memory.MemoryKey;
+import org.jetbrains.annotations.NotNull;
+import java.util.function.Predicate;
+import java.util.function.Function;
+import org.bukkit.entity.LivingEntity;
+import java.util.Map;
+
+public interface Tasks {
+ /**
+ * Instructs the entity to get within N blocks of the block location.
+ */
+ public <Entity extends Mob> @NotNull Task<Entity> walkToWalkTarget(int minRunTime, int maxRunTime);
+ /**
+ * Instructs the entity to swim if it is in water.
+ */
+ public <Entity extends Mob> @NotNull Task<Entity> swimIfInWater(float chance);
+ /**
+ * Instructs the entity to panic if it is hit, freezing, or on fire.
+ */
+ public <Entity extends Mob> @NotNull Task<Entity> panicOnDamage(float speed);
+ /**
+ * Instructs the entity to change its look target to the closest target
+ * for which the predicate returns true.
+ */
+ public <Entity extends LivingEntity> @NotNull Task<Entity> setLookTarget(@NotNull Predicate<LivingEntity> predicate, float maximumDistance);
+ /**
+ * Instructs the entity to set its walk target to the look target if it matches the given predicate.
+ *
+ * @param speed a function defining the speed at which the entity will walk to the given target
+ * @param completionRange the distance in blocks that the entity will attempt to get within
+ */
+ public <Entity extends LivingEntity> @NotNull Task<Entity> setWalkTargetToLookTarget(@NotNull Predicate<LivingEntity> predicate, @NotNull Function<LivingEntity, Float> speed, int completionRange);
+ /**
+ * Instructs the entity to look at the look target.
+ */
+ public <Entity extends Mob> @NotNull Task<Entity> lookAtLookTarget(int minRunTime, int maxRunTime);
+ /**
+ * Instructs the entity to run one of the given tasks with weighted probability.
+ */
+ public <Entity extends LivingEntity> @NotNull Task<Entity> runOneOf(@NotNull Map<Task<? super Entity>, Integer> tasks);
+}
diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java
index a505064565f7f029be8727b1951d9ef67c3acf2c..a60513d5541938bb0b2ca43496d31276a383f4f5 100644
--- a/src/main/java/io/papermc/paper/registry/RegistryKey.java
+++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java
@@ -25,6 +25,8 @@ import org.bukkit.inventory.meta.trim.TrimPattern;
import org.bukkit.map.MapCursor;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
+import io.papermc.paper.entity.ai.Activity;
+import io.papermc.paper.entity.ai.SensorType;
import static io.papermc.paper.registry.RegistryKeyImpl.create;
@@ -51,6 +53,16 @@ public sealed interface RegistryKey<T> extends Keyed permits RegistryKeyImpl {
* @see io.papermc.paper.registry.keys.GameEventKeys
*/
RegistryKey<GameEvent> GAME_EVENT = create("game_event");
+ /**
+ * Built-in registry for activities.
+ * @see io.papermc.paper.registry.keys.ActivityKeys
+ */
+ RegistryKey<Activity> ACTIVITY = create("activity");
+ /**
+ * Built-in registry for sensor types.
+ * @see io.papermc.paper.registry.keys.SensorTypeKeys
+ */
+ RegistryKey<SensorType<?>> SENSOR_TYPE = create("sensor_type");
/**
* Built-in registry for structure types.
* @see io.papermc.paper.registry.keys.StructureTypeKeys
diff --git a/src/main/java/org/bukkit/Registry.java b/src/main/java/org/bukkit/Registry.java
index d2eaf28999d437bf23ba5c7408124ad69d71fc79..89b8d1c71a4b4eb546c69a12beea6c455b5ac581 100644
--- a/src/main/java/org/bukkit/Registry.java
+++ b/src/main/java/org/bukkit/Registry.java
@@ -293,6 +293,20 @@ public interface Registry<T extends Keyed> extends Iterable<T> {
*/
Registry<GameEvent> GAME_EVENT = io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.registry.RegistryKey.GAME_EVENT); // Paper
// Paper start
+ /**
+ * Activities.
+ *
+ * @see io.papermc.paper.entity.ai.Activity
+ */
+ Registry<io.papermc.paper.entity.ai.Activity> ACTIVITY = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.entity.ai.Activity.class), "No registry present for Activity. This is a bug.");
+
+ /**
+ * Sensor types.
+ *
+ * @see io.papermc.paper.entity.ai.SensorType
+ */
+ Registry<io.papermc.paper.entity.ai.SensorType> SENSOR_TYPE = Objects.requireNonNull(io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(io.papermc.paper.entity.ai.SensorType.class), "No registry present for SensorType. This is a bug.");
+
/**
* Configured structures.
* @see io.papermc.paper.world.structure.ConfiguredStructure
diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
index c8595ffcfcbdd79794d464415287d46acef72b72..1dca705e757933074786230df73ddf8f32fefb63 100644
--- a/src/main/java/org/bukkit/Server.java
+++ b/src/main/java/org/bukkit/Server.java
@@ -2542,4 +2542,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
boolean isOwnedByCurrentRegion(@NotNull Entity entity);
// Paper end - Folia region threading API
+ // Paper start - activities and tasks API
+ @NotNull
+ io.papermc.paper.entity.ai.Tasks getTasks();
+
+ @NotNull
+ <E extends org.bukkit.entity.LivingEntity> io.papermc.paper.entity.ai.Brain<E> getBrain(@NotNull E entity);
+ // Paper end
}

View File

@ -0,0 +1,381 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Janet Blackquill <uhhadd@gmail.com>
Date: Fri, 10 May 2024 17:33:37 -0400
Subject: [PATCH] Implement preliminary API for working with brains AI
diff --git a/src/main/java/io/papermc/paper/entity/ai/PaperActivity.java b/src/main/java/io/papermc/paper/entity/ai/PaperActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..98916794a4e9f4316d9f0bbb824a40990e3879e7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/PaperActivity.java
@@ -0,0 +1,36 @@
+package io.papermc.paper.entity.ai;
+
+import net.minecraft.world.entity.schedule.Activity;
+import net.minecraft.core.registries.Registries;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Registry;
+import org.bukkit.craftbukkit.CraftRegistry;
+import org.bukkit.craftbukkit.util.Handleable;
+
+public class PaperActivity implements io.papermc.paper.entity.ai.Activity, Handleable<Activity> {
+ final private NamespacedKey key;
+ final private Activity handle;
+
+ public static io.papermc.paper.entity.ai.Activity minecraftToBukkit(Activity minecraft) {
+ return CraftRegistry.minecraftToBukkit(minecraft, Registries.ACTIVITY, Registry.ACTIVITY);
+ }
+ public static Activity bukkitToMinecraft(io.papermc.paper.entity.ai.Activity bukkit) {
+ return CraftRegistry.bukkitToMinecraft(bukkit);
+ }
+
+ public PaperActivity(NamespacedKey key, Activity handle) {
+ this.key = key;
+ this.handle = handle;
+ }
+
+ @Override
+ public Activity getHandle() {
+ return this.handle;
+ }
+
+ @Override
+ public NamespacedKey getKey() {
+ if (true) return java.util.Objects.requireNonNull(org.bukkit.Registry.ACTIVITY.getKey(this), () -> this + " doesn't have a key");
+ return this.key;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/entity/ai/PaperBrain.java b/src/main/java/io/papermc/paper/entity/ai/PaperBrain.java
new file mode 100644
index 0000000000000000000000000000000000000000..911c2370e2ee97e3a29a136f34abf3601127ed34
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/PaperBrain.java
@@ -0,0 +1,77 @@
+package io.papermc.paper.entity.ai;
+
+import java.util.List;
+import org.bukkit.entity.LivingEntity;
+import net.minecraft.world.entity.ai.Brain;
+import net.minecraft.world.entity.ai.memory.MemoryModuleType;
+import net.minecraft.world.entity.ai.memory.MemoryStatus;
+import com.google.common.collect.ImmutableList;
+import net.minecraft.world.entity.ai.behavior.BehaviorControl;
+import org.jetbrains.annotations.NotNull;
+import org.bukkit.entity.Entity;
+import org.bukkit.craftbukkit.entity.CraftEntity;
+import org.bukkit.Location;
+import net.minecraft.world.entity.ai.memory.WalkTarget;
+import org.bukkit.craftbukkit.util.CraftLocation;
+
+public class PaperBrain<Paper extends LivingEntity, Minecraft extends net.minecraft.world.entity.LivingEntity> implements io.papermc.paper.entity.ai.Brain<Paper> {
+ public final Brain<Minecraft> handle;
+
+ public PaperBrain(Brain<Minecraft> handle) {
+ this.handle = handle;
+ }
+ public Brain<Minecraft> getHandle() {
+ return this.handle;
+ }
+
+ @Override
+ public void useDefaultActivity() {
+ this.getHandle().useDefaultActivity();
+ }
+
+ @Override
+ public void useActivityIfPossible(@NotNull Activity activity) {
+ this.getHandle().setActiveActivityIfPossible(((PaperActivity)activity).getHandle());
+ }
+
+ @Override
+ public void setTasksForActivity(@NotNull Activity activity, int begin, @NotNull List<Task<Paper>> tasks) {
+ this.getHandle().addActivity(
+ ((PaperActivity)activity).getHandle(),
+ begin,
+ ImmutableList.<BehaviorControl<Minecraft>>copyOf(tasks.stream().map(task -> ((PaperTask<Paper, Minecraft>)task).getHandle()).iterator())
+ );
+ }
+
+ @Override
+ public void setSensors(@NotNull List<SensorType<Paper>> sensors) {
+ this.getHandle().setSensors(
+ sensors.stream().map(sensor -> ((PaperSensorType<Paper, net.minecraft.world.entity.ai.sensing.Sensor<Minecraft>>)sensor).getHandle()).toList()
+ );
+ }
+
+ @Override
+ public void clearActivities() {
+ this.getHandle().removeAllBehaviors();
+ }
+
+ @Override
+ public void setDefaultActivity(@NotNull Activity activity) {
+ this.getHandle().setDefaultActivity(((PaperActivity)activity).getHandle());
+ }
+
+ @Override
+ public boolean isActive(@NotNull Activity activity) {
+ return this.getHandle().isActive(((PaperActivity)activity).getHandle());
+ }
+
+ @Override
+ public void setWalkTarget(@NotNull Location location, float speed, int completeWithinDistance) {
+ this.getHandle().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(CraftLocation.toBlockPosition(location), speed, completeWithinDistance));
+ }
+
+ @Override
+ public void setWalkTarget(@NotNull Entity entity, float speed, int completeWithinDistance) {
+ this.getHandle().setMemory(MemoryModuleType.WALK_TARGET, new WalkTarget(((CraftEntity)entity).getHandle(), speed, completeWithinDistance));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/entity/ai/PaperSensorType.java b/src/main/java/io/papermc/paper/entity/ai/PaperSensorType.java
new file mode 100644
index 0000000000000000000000000000000000000000..a36a1855cb9b0098b1e8c7761f6d5a201421b1be
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/PaperSensorType.java
@@ -0,0 +1,26 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.NamespacedKey;
+import net.minecraft.world.entity.ai.sensing.Sensor;
+import net.minecraft.world.entity.ai.sensing.SensorType;
+import org.bukkit.craftbukkit.util.Handleable;
+
+public class PaperSensorType<Paper extends LivingEntity, Minecraft extends Sensor<?>> implements io.papermc.paper.entity.ai.SensorType<Paper>, Handleable<SensorType<Minecraft>> {
+ final private NamespacedKey key;
+ final private SensorType<Minecraft> handle;
+
+ public PaperSensorType(NamespacedKey key, SensorType<Minecraft> handle) {
+ this.key = key;
+ this.handle = handle;
+ }
+ public SensorType<Minecraft> getHandle() {
+ return this.handle;
+ }
+
+ @Override
+ public NamespacedKey getKey() {
+ if (true) java.util.Objects.requireNonNull(org.bukkit.Registry.SENSOR_TYPE.getKey(this), () -> this + " doesn't have a key");
+ return this.key;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/entity/ai/PaperTask.java b/src/main/java/io/papermc/paper/entity/ai/PaperTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..870ff6a179b62302c1b6dbf603fd5621396dd964
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/PaperTask.java
@@ -0,0 +1,16 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.entity.LivingEntity;
+
+import net.minecraft.world.entity.ai.behavior.BehaviorControl;
+
+public class PaperTask<Paper extends LivingEntity, Minecraft extends net.minecraft.world.entity.LivingEntity> implements io.papermc.paper.entity.ai.Task {
+ final private BehaviorControl<Minecraft> handle;
+
+ public PaperTask(BehaviorControl<Minecraft> handle) {
+ this.handle = handle;
+ }
+ public BehaviorControl<Minecraft> getHandle() {
+ return this.handle;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/entity/ai/PaperTasks.java b/src/main/java/io/papermc/paper/entity/ai/PaperTasks.java
new file mode 100644
index 0000000000000000000000000000000000000000..433fcfdbb0b0112f2e80e71fc9bea19ad62d32b5
--- /dev/null
+++ b/src/main/java/io/papermc/paper/entity/ai/PaperTasks.java
@@ -0,0 +1,54 @@
+package io.papermc.paper.entity.ai;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Mob;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.memory.MemoryKey;
+import net.minecraft.world.entity.ai.behavior.BehaviorControl;
+import net.minecraft.world.entity.ai.behavior.MoveToTargetSink;
+import net.minecraft.world.entity.ai.behavior.LookAtTargetSink;
+import net.minecraft.world.entity.ai.behavior.Swim;
+import net.minecraft.world.entity.ai.behavior.AnimalPanic;
+import net.minecraft.world.entity.ai.behavior.SetEntityLookTarget;
+import net.minecraft.world.entity.ai.behavior.SetWalkTargetFromLookTarget;
+import net.minecraft.world.entity.ai.behavior.RunOne;
+import org.bukkit.craftbukkit.entity.memory.CraftMemoryKey;
+import org.jetbrains.annotations.NotNull;
+import java.util.function.Predicate;
+import java.util.function.Function;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import com.mojang.datafixers.util.Pair;
+
+public class PaperTasks implements io.papermc.paper.entity.ai.Tasks {
+ public <Entity extends Mob> @NotNull Task<Entity> walkToWalkTarget(int minRunTime, int maxRunTime) {
+ return new PaperTask(new MoveToTargetSink(minRunTime, maxRunTime));
+ }
+ public <Entity extends Mob> @NotNull Task<Entity> swimIfInWater(float chance) {
+ return new PaperTask(new Swim(chance));
+ }
+ public <Entity extends Mob> @NotNull Task<Entity> panicOnDamage(float speed) {
+ return new PaperTask(new AnimalPanic(speed));
+ }
+ public <Entity extends LivingEntity> @NotNull Task<Entity> setLookTarget(Predicate<LivingEntity> predicate, float maximumDistance) {
+ return new PaperTask(SetEntityLookTarget.create(nmsEntity -> predicate.test(nmsEntity.getBukkitLivingEntity()), maximumDistance));
+ }
+ public <Entity extends LivingEntity> @NotNull Task<Entity> setWalkTargetToLookTarget(Predicate<LivingEntity> predicate, Function<LivingEntity, Float> speed, int completionRange) {
+ return new PaperTask(SetWalkTargetFromLookTarget.create(
+ nmsEntity -> predicate.test(nmsEntity.getBukkitLivingEntity()),
+ nmsEntity -> speed.apply(nmsEntity.getBukkitLivingEntity()),
+ completionRange
+ ));
+ }
+ public <Entity extends Mob> @NotNull Task<Entity> lookAtLookTarget(int minRunTime, int maxRunTime) {
+ return new PaperTask(new LookAtTargetSink(minRunTime, maxRunTime));
+ }
+ public <Bukkit extends LivingEntity> @NotNull Task<Bukkit> runOneOf(Map<Task<? super Bukkit>, Integer> tasks) {
+ List<Pair<BehaviorControl<? super net.minecraft.world.entity.LivingEntity>, Integer>> mcTasks = new ArrayList<>(tasks.size());
+ for (Map.Entry<Task<? super Bukkit>, Integer> entry : tasks.entrySet()) {
+ mcTasks.add(new Pair(((PaperTask)entry.getKey()).getHandle(), entry.getValue()));
+ }
+ return new PaperTask(new RunOne(mcTasks));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
index 183e168afcc4302bc1e3274a89835f1f60e4bbd6..c7b98c2e4646bdc0045e728675e38ebf4d4a7ddd 100644
--- a/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
@@ -33,6 +33,10 @@ import org.bukkit.potion.PotionEffectType;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
+import io.papermc.paper.entity.ai.Activity;
+import io.papermc.paper.entity.ai.PaperActivity;
+import io.papermc.paper.entity.ai.SensorType;
+import io.papermc.paper.entity.ai.PaperSensorType;
import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly;
import static io.papermc.paper.registry.entry.RegistryEntry.entry;
@@ -57,6 +61,8 @@ public final class PaperRegistries {
entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new),
entry(Registries.MOB_EFFECT, RegistryKey.MOB_EFFECT, PotionEffectType.class, CraftPotionEffectType::new),
entry(Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, StructureType.class, CraftStructureType::new),
+ entry(Registries.ACTIVITY, RegistryKey.ACTIVITY, Activity.class, PaperActivity::new),
+ entry(Registries.SENSOR_TYPE, (RegistryKey<SensorType>)(Object)RegistryKey.SENSOR_TYPE, SensorType.class, PaperSensorType::new),
// data-drivens
entry(Registries.STRUCTURE, RegistryKey.STRUCTURE, Structure.class, CraftStructure::new).delayed(),
diff --git a/src/main/java/net/minecraft/world/entity/ai/Brain.java b/src/main/java/net/minecraft/world/entity/ai/Brain.java
index afbb027021acfbe25d534a84f1750e420bbde6e0..c988bb4c15fb2c97b13e0abb16c2423e6eb617b0 100644
--- a/src/main/java/net/minecraft/world/entity/ai/Brain.java
+++ b/src/main/java/net/minecraft/world/entity/ai/Brain.java
@@ -79,7 +79,7 @@ public class Brain<E extends LivingEntity> {
}
public <T> DataResult<Brain<E>> decode(DynamicOps<T> dynamicOps, MapLike<T> mapLike) {
- MutableObject<DataResult<Builder<Brain.MemoryValue<?>>>> mutableObject = new MutableObject<>(
+ MutableObject<DataResult<Builder<Brain.MemoryValue<?>>>> dataResultMutableObject = new MutableObject<>( // Paper - fix decompilation name collision
DataResult.success(ImmutableList.builder())
);
mapLike.entries()
@@ -91,10 +91,10 @@ public class Brain<E extends LivingEntity> {
DataResult<? extends Brain.MemoryValue<?>> dataResult2 = dataResult.flatMap(
memoryType -> this.captureRead((MemoryModuleType<T>)memoryType, dynamicOps, (T)pair.getSecond())
);
- mutableObject.setValue(mutableObject.getValue().apply2(Builder::add, dataResult2));
+ dataResultMutableObject.setValue(dataResultMutableObject.getValue().apply2(Builder::add, dataResult2)); // Paper - fix decompilation name collision
}
);
- ImmutableList<Brain.MemoryValue<?>> immutableList = mutableObject.getValue()
+ ImmutableList<Brain.MemoryValue<?>> immutableList = dataResultMutableObject.getValue() // Paper - fix decompilation name collision
.resultOrPartial(Brain.LOGGER::error)
.map(Builder::build)
.orElseGet(ImmutableList::of);
@@ -179,29 +179,40 @@ public class Brain<E extends LivingEntity> {
this.setMemoryInternal(type, value.map(ExpirableValue::of));
}
+ // Paper start - allow overriding sensors
+ public void setSensors(Collection<? extends SensorType<? extends Sensor<? super E>>> newSensors) {
+ this.sensors.clear();
+ for (SensorType<? extends Sensor<? super E>> sensorType : newSensors) {
+ this.sensors.put(sensorType, (Sensor<? super E>)sensorType.create());
+ }
+ }
+ // Paper end
+
<U> void setMemoryInternal(MemoryModuleType<U> type, Optional<? extends ExpirableValue<?>> memory) {
- if (this.memories.containsKey(type)) {
+ // Paper start - allow custom memories to be set on any brain
+ // if (this.memories.containsKey(type)) {
if (memory.isPresent() && this.isEmptyCollection(memory.get().getValue())) {
this.eraseMemory(type);
} else {
this.memories.put(type, memory);
}
- }
+ // }
+ // Paper end - allow custom memories to be set on any brain
}
public <U> Optional<U> getMemory(MemoryModuleType<U> type) {
- Optional<? extends ExpirableValue<?>> optional = this.memories.get(type);
+ Optional<? extends ExpirableValue<?>> optional = (Optional<? extends ExpirableValue<U>>)this.memories.get(type); // Paper - decompilation fix
if (optional == null) {
throw new IllegalStateException("Unregistered memory fetched: " + type);
} else {
- return optional.map(ExpirableValue::getValue);
+ return optional.map(x -> (U)x.getValue()); // Paper - decompilation fix
}
}
@Nullable
public <U> Optional<U> getMemoryInternal(MemoryModuleType<U> type) {
- Optional<? extends ExpirableValue<?>> optional = this.memories.get(type);
- return optional == null ? null : optional.map(ExpirableValue::getValue);
+ Optional<? extends ExpirableValue<?>> optional = (Optional<? extends ExpirableValue<U>>)this.memories.get(type); // Paper - decompilation fix
+ return optional == null ? null : optional.map(x -> (U)x.getValue()); // Paper - decompilation fix
}
public <U> long getTimeUntilExpiry(MemoryModuleType<U> type) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 57db399bc1d3b6b015740b059987bc8d9bcc3101..d7aea0c207c553ba6b2aa20fadd810a61b1a0e04 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -3278,4 +3278,17 @@ public final class CraftServer implements Server {
return this.potionBrewer;
}
// Paper end
+ // Paper begin - activities and tasks AI API
+ private final io.papermc.paper.entity.ai.Tasks tasks = new io.papermc.paper.entity.ai.PaperTasks();
+
+ @Override
+ public io.papermc.paper.entity.ai.Tasks getTasks() {
+ return this.tasks;
+ }
+
+ @Override
+ public <E extends org.bukkit.entity.LivingEntity> io.papermc.paper.entity.ai.Brain<E> getBrain(E entity) {
+ return new io.papermc.paper.entity.ai.PaperBrain(((org.bukkit.craftbukkit.entity.CraftLivingEntity)entity).getHandle().getBrain());
+ }
+ // Paper end - activities and tasks AI API
}
diff --git a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
index 69a6a8419947a0617405e8931193f88d0dc5c3c4..3d3cb859f82356c7b32eff13ee3073851b10b80c 100644
--- a/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
+++ b/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java
@@ -49,6 +49,7 @@ public class RegistriesArgumentProvider implements ArgumentsProvider {
DATA.add(Arguments.of(RegistryKey.TRIM_PATTERN, TrimPattern.class, Registries.TRIM_PATTERN, CraftTrimPattern.class, net.minecraft.world.item.armortrim.TrimPattern.class));
DATA.add(Arguments.of(RegistryKey.DAMAGE_TYPE, DamageType.class, Registries.DAMAGE_TYPE, CraftDamageType.class, net.minecraft.world.damagesource.DamageType.class));
DATA.add(Arguments.of(RegistryKey.WOLF_VARIANT, Wolf.Variant.class, Registries.WOLF_VARIANT, CraftWolf.CraftVariant.class, net.minecraft.world.entity.animal.WolfVariant.class));
+ DATA.add(Arguments.of(RegistryKey.ACTIVITY, io.papermc.paper.entity.ai.Activity.class, Registries.ACTIVITY, io.papermc.paper.entity.ai.PaperActivity.class, net.minecraft.world.entity.schedule.Activity.class)); // Paper - add activity registry
}
@Override

View File

@ -2,11 +2,72 @@ package io.papermc.testplugin;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Sniffer;
import org.bukkit.entity.Minecart;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.entity.memory.MemoryKey;
import java.util.Collections;
import io.papermc.paper.registry.keys.ActivityKeys;
import org.bukkit.Registry;
import org.bukkit.NamespacedKey;
import java.util.Map;
import io.papermc.paper.registry.keys.SensorTypeKeys;
public final class TestPlugin extends JavaPlugin implements Listener {
public class SnifferCommand extends org.bukkit.command.Command {
public SnifferCommand() {
super("sniffer");
}
public boolean execute(CommandSender sender, String label, String []params) {
var player = (Player)sender;
var sniffer = (Sniffer)player.getWorld()
.spawnEntity(player.getLocation(), EntityType.SNIFFER);
var target = player.getLocation();
target.add(10, 0, 0);
var server = Bukkit.getServer();
var nearestLivingEntitiesKey = SensorTypeKeys.NEAREST_LIVING_ENTITIES.key();
var nearestLivingEntities = Registry.SENSOR_TYPE.get(new NamespacedKey(nearestLivingEntitiesKey.namespace(), nearestLivingEntitiesKey.value()));
var hurtByKey = SensorTypeKeys.HURT_BY.key();
var hurtBy = Registry.SENSOR_TYPE.get(new NamespacedKey(hurtByKey.namespace(), hurtByKey.value()));
var nearestPlayersKey = SensorTypeKeys.NEAREST_PLAYERS.key();
var nearestPlayers = Registry.SENSOR_TYPE.get(new NamespacedKey(nearestPlayersKey.namespace(), nearestPlayersKey.value()));
var snifferTemptationsKey = SensorTypeKeys.SNIFFER_TEMPTATIONS.key();
var snifferTemptations = Registry.SENSOR_TYPE.get(new NamespacedKey(snifferTemptationsKey.namespace(), snifferTemptationsKey.value()));
var core = Registry.SENSOR_TYPE.get(new NamespacedKey(ActivityKeys.CORE.key().namespace(), ActivityKeys.CORE.key().value()));
// var core = Registry.ACTIVITY.get(new NamespacedKey(ActivityKeys.CORE.key().namespace(), ActivityKeys.CORE.key().value()));
// var idle = Registry.ACTIVITY.get(new NamespacedKey(ActivityKeys.IDLE.key().namespace(), ActivityKeys.IDLE.key().value()));
var brain = Bukkit.getServer().getBrain(sniffer);
var tasks = server.getTasks();
// brain.setWalkTarget(target, 1.25F, 0);
// brain.clearActivities();
brain.setSensors(List.of(nearestLivingEntities, hurtBy, nearestPlayers, snifferTemptations));
// brain.setTasksForActivity(core, 0, List.of(
// tasks.swimIfInWater(0.8f),
// tasks.panicOnDamage(9.0f),
// tasks.setLookTarget(entity -> entity.getVehicle() instanceof Minecart, 30f),
// tasks.setWalkTargetToLookTarget(entity -> true, entity -> 9.0f, 3)
// ));
// brain.setTasksForActivity(idle, 0, List.of(
// tasks.lookAtLookTarget(150, 250),
// tasks.walkToWalkTarget(150, 250)
// ));
// brain.useActivityIfPossible(idle);
// sender.sendMessage(brain.isActive(idle) ? "Yes!" : "no no no");
return true;
}
}
@Override
public void onEnable() {
this.getServer().getCommandMap().register("sniffer", new SnifferCommand());
this.getServer().getPluginManager().registerEvents(this, this);
}
}