Paper
Paper copied to clipboard
WIP brain API
This API is supposed to completely expose the "Brain" API that are used by certain entities such as goats, axolotls and villagers. However this api ALSO allows manually ticking brains of all entities.
This API is HEAVILY in WIP. I have created this pull request in order to collect as much feedback as I can here, so there is a lot of stuff like imports, access transformers, and paper comments I have to still do.
Some things I need help with:
- Registering Custom Types
- As of 1.18.2 you can no longer register stuff like this during runtime, how should I handle the registry freezing for registering custom memories/sensors/etc?
- Memories
- Memories that are serialized don't get loaded correctly, basically see the big comment in the Brain diff.
- Is this level of exposure necessary?
- Although there is a lot you have access to here, I worry that this may become quite a huge burden due to the amount of hacks that this requires.
I pushed a rough example in the test plugin, the pickled brain! See https://canary.discord.com/channels/289587909051416579/925530366192779286/989010753371652106 for the behavior. ALL FEEDBACK WELCOME!
If you would like to use the brain debugger, use this fabric mod. This will allow the debugger to render.
enable-brain-debug-1.0-SNAPSHOT.zip
private final MemoryKey<Boolean> IS_SCARED = Bukkit.createMemoryKey(NamespacedKey.fromString("scared", this), Boolean.class);
private final SensorKey SCARIES_SENSOR = Bukkit.createSensorKey(NamespacedKey.fromString("scary_mobs_finder", this));
private final ActivityKey SCARED_ACTIVITY = Bukkit.createActivityKey(NamespacedKey.fromString("scared", this));
I updated how custom keys are created. This allowed me to get rid of the maps, and instead just do some instance checking in the utility classes (which have now been merged as well).
I have included a new example that further shows what this system can use and have documented most of the api. Opinions appreciated. :)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This will be a really nice API, I have a team which makes a "Vanilla with additions" server, so we develop our own APIs and libraries for "mod-like" development with plugins, we do many NMS hacks to reach "modded" experience for players, obviously making hacks is bad, I tried to make something like "Brain API" via VarHandles and reflection, but it looked very dirty, and probably the performance in this case would be very poor. so as a plugin developer I need fast and minimally hacky API for interacting with entity AI. I plan contribute in this WIP API, because I think that it will be useful for other people like me.
Hello everyone, I did some refactor of this draft API. (Owen knows about it) This refactor is about making better backward compatibility with Bukkit MemoryKey API, cleaner implementation and better maintainability of Brain API. I tested it, but anyway it can contain some bugs. Feel free to give a feedback about this refactor!
API changes:
- Created
MemoryModuleType<U>API interface which hasNamespacedKeygetter andOptional<MemoryKey<U>>getter, this getter returnsOptionalhaving some value only if thisMemoryModuleTypeis vanilla and represented in MemoryKey API. - Created
MemoryManagerinterface (singletone likeBrainManager) where I transferred methods manipulating with memory (sensor, activity, behaviour methods aren't transferred) fromBrainManager, edited them withMemoryModuleTypeand added additionalsetMemory()andgetMemory()methods for manipulating withMemoryModuleTypeinstead ofMemoryKey. - Changed usage of
MemoryKeyinBrainManager,Sensorand etc. toMemoryModuleType. - Deleted methods for creation of custom MemoryKeys in Bukkit and Server and replaced them with creation of API
MemoryModuleTypeboth from BukkitMemoryKeyandNamespacedKey.
Impl details:
MemoryManagerhasPaperMemoryManagerimplementation which has memory methods implemented and also contains twoHashBiMaps, firstBiMapcontains registered custom types of APIMemoryModuleTypes and second one is a cache forMemoryModuleTypes representing vanilla memories.- API interface
MemoryModuleTypehasPaperMemoryModuleTypeimplementation containing NMSMemoryModuleType, its own unique namespaced key and optionallyMemoryKey. This impl has two ways of creation, first one creates fromNamespacedKeyand second one creates fromMemoryKey, first makes new NMSMemoryModuleTypeputsNamespacedKeyand itself in registry defined inPaperMemoryManagerand also puts new NMSMemoryModuleTypeinto NMSRegistry. I'll show later why NMSRegistryis needed. Second gets value from the cache defined inPaperMemoryManagerif it exists, if not, it gets NMSMemoryModuleTypethroughCraftMemoryKey.fromMemoryKey(key)and inserts itsNamespacedKeyinto APIMemoryModuleType's one. And puts this instance to the cache and returns. - I added in
PaperBrainUtilthree methods, first isgetHandle()for getting NMSMemoryModuleTypefor API one, andtoPaper()which gets APIMemoryModuleTypesfrom NMS one. How? It is the place where NMSRegistryhelps. This method looks like:
public static PaperMemoryModuleType<?> toPaper(net.minecraft.world.entity.ai.memory.MemoryModuleType<?> nms) {
// custom types actually register in NMS Registry, that's why we are getting them from it.
final PaperMemoryModuleType<?> customMemoryType = (PaperMemoryModuleType<?>) PaperMemoryManager.INSTANCE.getTypeByKey(CraftNamespacedKey.fromMinecraft(Registry.MEMORY_MODULE_TYPE.getKey(nms)));
// Here we are getting Vanilla type (from cache) if custom type doesn't exist.
return customMemoryType == null ? PaperMemoryModuleType.of(CraftMemoryKey.toMemoryKey(nms)) : customMemoryType;
}
It is the main method which makes possible refactoring of everything else. And the third is handleIfVanilla(), this method operates with memory value and maps this value to NMS if Optional<MemoryKey<U>> presents in API MemoryModuleType, it is needed for proper manipulation with memory values of Vanilla MemoryModuleTypes in MemoryManager.
Code examples.
This is how this API MemoryModuleType creates:
public static final MemoryModuleType<List<Squid>> SQUID_CANDIDATES = Bukkit.createMemoryModuleType(NamespacedKey.fromString("squid_candidates"));
public static final MemoryModuleType<List<Parrot>> NEARBY_PARROTS = Bukkit.createMemoryModuleType(NamespacedKey.fromString("nearby_parrots"));
public static final MemoryModuleType<Location> MEETING_POINT = Bukkit.createMemoryModuleType(MemoryKey.MEETING_POINT);
This is how to manipulate with memory in this refactor:
// Obtaining MemoryManager from BrainManager
MemoryManager memoryManager = Bukkit.getBrainManager().getMemoryManager();
// Getting memory value of given entity.
List<Squid> squids = memoryManager.getMemory(entity, TestPlugin.SQUID_CANDIDATES).orElse(new ArrayList<>());
if (target == null || target.isDead()) {
if (squids.isEmpty()) {
// Erasing it.
memoryManager.eraseMemory(entity, TestPlugin.SQUID_CANDIDATES);
memoryManager.eraseMemory(entity, TestPlugin.SQUID_RAGE);
return;
}
target = squids.remove(0);
} else {
entity.getPathfinder().moveTo(target);
}
Afterword.
As Owen said: "ALL FEEDBACK WELCOME!", I wanna see Opinions about this refactor, especially about its maintainability, and impl.
Responding to @ExtraExtremeERA Couple things here...
-
That new interface
MemoryModuleType<U>, I don't think it fits well unless you are just replacing all of MemoryKey with it, cause now there are two MemoryKey APIs essentially instead of just one. I prefer Owen's PaperMemoryKey (but that has its own problems too which ill comment on later) -
I like having concept of separating the Memory stuff into a separate interface, but I haven't really been a fan of the whole Manager interface. Why not just make that interface one that Entities extend directly and then we implement it in some
PaperMemoryHolderinterface that we make CraftLivingEntity extend?
- I like having concept of separating the Memory stuff into a separate interface, but I haven't really been a fan of the whole Manager interface. Why not just make that interface one that Entities extend directly and then we implement it in some
PaperMemoryHolderinterface that we make CraftLivingEntity extend?
Regarding this point, I would defo like your opinion on this. The issue is that the type of the entity is required in some cases, and in the future when most likely more entities are moved over to use the brain system it will cause the generic to be tossed everywhere. See: https://canary.discord.com/channels/289587909051416579/925530366192779286/925667741984243723
Ping me in contrib tho and we can discuss more, cause i'd like more feedback.
Rebased to 1.19, redid the example, asking for feedback, you can now tick all entity brains, etc.
Hey, would it be possible to add the BrainAPI for the camels? For example, that I can prevent the camels from lying down.
oh, nvm, I deleted my previous comment, because I forgot that it is the goal of this API for some reason. The only thing it needs to support camels is a rebase to put a BrainHolder interface in it. And if there are any new activities or memories or whatever for them there would be a need to implement them using code gen or whatever.