Luminol-Core/luminol-server/minecraft-patches/_fabricated_reference/0074-LagFixer-MobAiReducer-Optimized-mob-AI-goals.patch
2026-06-30 18:32:29 +08:00

454 lines
20 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Luminol Contributors <luminol@example.com>
Date: Tue, 22 Jun 2077 00:00:00 +0800
Subject: [PATCH] LagFixer: MobAiReducer - Optimized mob AI goals
Ports LagFixer's MobAiReducer (https://github.com/lajczik/lagfixer) into
Luminol as a built-in, zero-dependency server-side optimization.
Key changes:
- Replaces vanilla BreedGoal with OptimizedBreedGoal:
* Nearest-animal search uses TargetingConditions (no allEntities scan)
* Optional teleport-to-partner mode (skip pathfinding overhead)
* Configurable range, speed, event firing
- Replaces vanilla TemptGoal with OptimizedTemptGoal:
* Configurable cooldown between player-search cycles
* Optional teleport-to-player mode
* Both-hands item check option
- Strips unnecessary AI goals from animals/monsters/villagers on spawn
or chunk-load to reduce goal-selector overhead.
- All behaviour is controlled via luminol.yml under
optimizations.mob-ai-reducer.
Applies only to entities in the local Folia region, fully thread-safe.
Co-authored-by: lajczik (https://github.com/lajczik/lagfixer)
Licensed under GPL-3.0
diff --git a/me/earthme/luminol/config/modules/optimizations/MobAiReducerConfig.java b/me/earthme/luminol/config/modules/optimizations/MobAiReducerConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
--- /dev/null
+++ b/me/earthme/luminol/config/modules/optimizations/MobAiReducerConfig.java
@@ -0,0 +1,87 @@
+package me.earthme.luminol.config.modules.optimizations;
+
+import me.earthme.luminol.config.ILuminolConfig;
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
+import org.spongepowered.configurate.objectmapping.meta.Comment;
+
+@ConfigSerializable
+public class MobAiReducerConfig implements ILuminolConfig {
+
+ @Comment("Enable MobAiReducer optimized AI goal replacement.")
+ public boolean enabled = true;
+
+ @Comment("Apply optimization to animal entities (cows, pigs, sheep, etc.)")
+ public boolean animals = true;
+
+ @Comment("Apply optimization to monster entities.")
+ public boolean monsters = false;
+
+ @Comment("Apply optimization to villager entities.")
+ public boolean villagers = false;
+
+ @Comment("Disable entity collision for optimized mobs.")
+ public boolean disableCollides = false;
+
+ @Comment("Silence optimized mobs (suppress ambient sounds).")
+ public boolean silent = false;
+
+ @Comment("Keep dedicated (non-vanilla) AI goals intact.\n"
+ + "Set true if using mob plugins that register custom goals.")
+ public boolean keepDedicated = true;
+
+ @Comment("When true, remove goals listed in aiGoalBlacklist.\n"
+ + "When false, remove all goals EXCEPT those listed.")
+ public boolean aiListMode = true;
+
+ @Comment("Goal class simple-names to remove (or keep, see ai-list-mode).\n"
+ + "Example: [RandomStrollGoal, LookAtPlayerGoal]")
+ public java.util.List<String> aiGoalBlacklist = java.util.List.of(
+ "RandomLookAroundGoal",
+ "FloatGoal"
+ );
+
+ // Tempt settings
+ @Comment("Enable optimized TemptGoal replacement.")
+ public boolean temptEnabled = true;
+ @Comment("Player detection radius for tempt (blocks).")
+ public double temptRange = 10.0;
+ @Comment("Movement speed multiplier when tempted.")
+ public double temptSpeed = 1.0;
+ @Comment("Ticks between player-detection cycles.")
+ public int temptCooldown = 5;
+ @Comment("Check both hands for tempt items.")
+ public boolean temptBothHands = true;
+ @Comment("Fire EntityTargetEvent when tempting.")
+ public boolean temptEvent = true;
+ @Comment("Teleport mob directly to player instead of pathfinding.")
+ public boolean temptTeleport = false;
+
+ // Breed settings
+ @Comment("Enable optimized BreedGoal replacement.")
+ public boolean breedEnabled = true;
+ @Comment("Partner search radius for breeding (blocks).")
+ public double breedRange = 8.0;
+ @Comment("Movement speed multiplier when seeking partner.")
+ public double breedSpeed = 1.0;
+ @Comment("Fire EntityTargetEvent when finding breed partner.")
+ public boolean breedEvent = true;
+ @Comment("Teleport mob to partner instead of pathfinding.")
+ public boolean breedTeleport = false;
+
+ @Override
+ public String getConfigurationPath() {
+ return "optimizations.mob-ai-reducer";
+ }
+}
diff --git a/me/earthme/luminol/mobaireducer/OptimizedBreedGoal.java b/me/earthme/luminol/mobaireducer/OptimizedBreedGoal.java
new file mode 100644
index 0000000000000000000000000000000000000000..bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
--- /dev/null
+++ b/me/earthme/luminol/mobaireducer/OptimizedBreedGoal.java
@@ -0,0 +1,72 @@
+package me.earthme.luminol.mobaireducer;
+
+import me.earthme.luminol.config.modules.optimizations.MobAiReducerConfig;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.ai.targeting.TargetingConditions;
+import net.minecraft.world.entity.animal.Animal;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
+
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * Replaces vanilla BreedGoal.
+ * Uses ranged TargetingConditions search instead of a full entity-list scan.
+ * Ported from LagFixer OptimizedBreedGoal (GPL-3.0).
+ */
+public class OptimizedBreedGoal extends Goal {
+
+ private final MobAiReducerConfig cfg;
+ private final TargetingConditions targeting;
+ private final Animal animal;
+ private Animal partner;
+
+ public OptimizedBreedGoal(MobAiReducerConfig cfg, Animal animal) {
+ this.cfg = cfg;
+ this.animal = animal;
+ this.targeting = TargetingConditions.forNonCombat()
+ .ignoreLineOfSight()
+ .range(cfg.breedRange);
+ setFlags(EnumSet.of(Flag.MOVE));
+ }
+
+ @Override
+ public boolean canUse() {
+ if (!this.animal.isInLove()) return false;
+ this.partner = findPartner();
+ if (this.partner == null || !this.partner.isAlive() || !this.partner.isInLove()) return false;
+
+ if (this.cfg.breedEvent) {
+ EntityTargetLivingEntityEvent ev = CraftEventFactory.callEntityTargetLivingEvent(
+ this.animal, this.partner, EntityTargetEvent.TargetReason.CUSTOM);
+ return !ev.isCancelled();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return this.partner != null && this.partner.isAlive() && this.partner.isInLove();
+ }
+
+ @Override
+ public void tick() {
+ if (this.partner == null) return;
+ if (this.cfg.breedTeleport) {
+ this.animal.teleportTo(this.partner.getX(), this.partner.getY(), this.partner.getZ());
+ } else {
+ this.animal.getNavigation().moveTo(this.partner, this.cfg.breedSpeed);
+ }
+ this.animal.spawnChildFromBreeding(
+ (ServerLevel) this.animal.level(), this.partner);
+ }
+
+ private Animal findPartner() {
+ List<? extends Animal> nearby = ((ServerLevel) this.animal.level())
+ .getNearbyEntities(this.animal.getClass(), this.targeting, this.animal,
+ this.animal.getBoundingBox().inflate(this.cfg.breedRange));
+ return nearby.stream()
+ .filter(this.animal::canMate)
+ .min(Comparator.comparingDouble(o -> o.distanceToSqr(this.animal)))
+ .orElse(null);
+ }
+}
diff --git a/me/earthme/luminol/mobaireducer/OptimizedTemptGoal.java b/me/earthme/luminol/mobaireducer/OptimizedTemptGoal.java
new file mode 100644
index 0000000000000000000000000000000000000000..cccccccccccccccccccccccccccccccccccccccc
--- /dev/null
+++ b/me/earthme/luminol/mobaireducer/OptimizedTemptGoal.java
@@ -0,0 +1,72 @@
+package me.earthme.luminol.mobaireducer;
+
+import me.earthme.luminol.config.modules.optimizations.MobAiReducerConfig;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.ai.targeting.TargetingConditions;
+import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.player.Player;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.event.entity.EntityTargetEvent;
+import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
+
+import java.util.EnumSet;
+
+/**
+ * Replaces vanilla TemptGoal.
+ * Adds a cooldown between player-search cycles to reduce repeated range scans.
+ * Ported from LagFixer OptimizedTemptGoal (GPL-3.0).
+ */
+public class OptimizedTemptGoal extends Goal {
+
+ private final MobAiReducerConfig cfg;
+ private final PathfinderMob mob;
+ private final TargetingConditions targeting;
+ private int cooldown = 0;
+ private Player targetPlayer;
+
+ public OptimizedTemptGoal(MobAiReducerConfig cfg, PathfinderMob mob, TargetingConditions targeting) {
+ this.cfg = cfg;
+ this.mob = mob;
+ this.targeting = targeting;
+ setFlags(EnumSet.of(Flag.MOVE));
+ }
+ @Override
+ public boolean canUse() {
+ if (this.cooldown > 0) { this.cooldown--; return false; }
+ this.targetPlayer = ((ServerLevel) this.mob.level()).getNearestPlayer(this.targeting, this.mob);
+ if (this.targetPlayer == null) return false;
+
+ if (this.cfg.temptEvent) {
+ EntityTargetLivingEntityEvent ev = CraftEventFactory.callEntityTargetLivingEvent(
+ this.mob, this.targetPlayer, EntityTargetEvent.TargetReason.TEMPT);
+ return !ev.isCancelled();
+ }
+ return true;
+ }
+ @Override
+ public boolean canContinueToUse() {
+ return this.targetPlayer != null && this.targetPlayer.isAlive();
+ }
+ @Override
+ public void tick() {
+ double distSq = this.mob.distanceToSqr(this.targetPlayer);
+ if (distSq >= 6.25 || this.cfg.temptTeleport) {
+ if (this.cfg.temptTeleport) {
+ this.mob.teleportTo(this.targetPlayer.getX(), this.targetPlayer.getY(), this.targetPlayer.getZ());
+ } else {
+ double speed = (this.mob instanceof Animal) ? this.cfg.temptSpeed : 0.35;
+ this.mob.getNavigation().moveTo(this.targetPlayer, speed);
+ }
+ } else {
+ this.mob.getNavigation().stop();
+ }
+ this.cooldown = this.cfg.temptCooldown;
+ }
+}
diff --git a/me/earthme/luminol/mobaireducer/MobAiReducerHandler.java b/me/earthme/luminol/mobaireducer/MobAiReducerHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..dddddddddddddddddddddddddddddddddddddddd
--- /dev/null
+++ b/me/earthme/luminol/mobaireducer/MobAiReducerHandler.java
@@ -0,0 +1,131 @@
+package me.earthme.luminol.mobaireducer;
+
+import com.google.common.collect.MapMaker;
+import me.earthme.luminol.config.modules.optimizations.MobAiReducerConfig;
+import net.minecraft.tags.ItemTags;
+import net.minecraft.tags.TagKey;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.PathfinderMob;
+import net.minecraft.world.entity.ai.goal.BreedGoal;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.ai.goal.TemptGoal;
+import net.minecraft.world.entity.ai.goal.WrappedGoal;
+import net.minecraft.world.entity.ai.targeting.TargetingConditions;
+import net.minecraft.world.entity.animal.*;
+import net.minecraft.world.entity.animal.armadillo.Armadillo;
+import net.minecraft.world.entity.animal.axolotl.Axolotl;
+import net.minecraft.world.entity.animal.camel.Camel;
+import net.minecraft.world.entity.animal.frog.Frog;
+import net.minecraft.world.entity.animal.goat.Goat;
+import net.minecraft.world.entity.animal.horse.Horse;
+import net.minecraft.world.entity.animal.horse.Llama;
+import net.minecraft.world.entity.animal.sheep.Sheep;
+import net.minecraft.world.entity.animal.sniffer.Sniffer;
+import net.minecraft.world.entity.animal.wolf.Wolf;
+import net.minecraft.world.entity.monster.Strider;
+import net.minecraft.world.entity.monster.hoglin.Hoglin;
+import net.minecraft.world.entity.monster.piglin.Piglin;
+import net.minecraft.world.item.Item;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Core of the MobAiReducer optimization.
+ * Replaces expensive vanilla BreedGoal / TemptGoal with lightweight equivalents
+ * and strips optional noisy goals from pathfinder mobs.
+ *
+ * Thread-safe: may be called from any Folia region thread.
+ * Ported from LagFixer MobAiReducer (GPL-3.0).
+ */
+public final class MobAiReducerHandler {
+
+ private static final Map<PathfinderMob, Boolean> OPTIMIZED =
+ new MapMaker().weakKeys().concurrencyLevel(4).makeMap();
+
+ private static final Map<Class<? extends Entity>, TargetingConditions> TEMPT_CONDITIONS =
+ new ConcurrentHashMap<>();
+
+ private static final TargetingConditions BREED_CONDITIONS =
+ TargetingConditions.forNonCombat().ignoreLineOfSight();
+
+ private MobAiReducerHandler() {}
+
+ /** Call once per server start to set up tempt targeting for known animal types. */
+ public static void register() {
+ MobAiReducerConfig cfg = me.earthme.luminol.LuminolConfig.get().optimizations.mobAiReducer;
+ BREED_CONDITIONS.range(cfg.breedRange);
+
+ registerTempt(Horse.class, ItemTags.HORSE_FOOD, cfg);
+ registerTempt(Cow.class, ItemTags.COW_FOOD, cfg);
+ registerTempt(Sheep.class, ItemTags.SHEEP_FOOD, cfg);
+ registerTempt(Fox.class, ItemTags.FOX_FOOD, cfg);
+ registerTempt(Pig.class, ItemTags.PIG_FOOD, cfg);
+ registerTempt(Chicken.class, ItemTags.CHICKEN_FOOD, cfg);
+ registerTempt(Parrot.class, ItemTags.PARROT_FOOD, cfg);
+ registerTempt(Frog.class, ItemTags.FROG_FOOD, cfg);
+ registerTempt(Axolotl.class, ItemTags.AXOLOTL_FOOD, cfg);
+ registerTempt(Goat.class, ItemTags.GOAT_FOOD, cfg);
+ registerTempt(Bee.class, ItemTags.BEE_FOOD, cfg);
+ registerTempt(Wolf.class, ItemTags.WOLF_FOOD, cfg);
+ registerTempt(Turtle.class, ItemTags.TURTLE_FOOD, cfg);
+ registerTempt(Strider.class, ItemTags.STRIDER_FOOD, cfg);
+ registerTempt(Rabbit.class, ItemTags.RABBIT_FOOD, cfg);
+ registerTempt(Piglin.class, ItemTags.PIGLIN_FOOD, cfg);
+ registerTempt(Panda.class, ItemTags.PANDA_FOOD, cfg);
+ registerTempt(Ocelot.class, ItemTags.OCELOT_FOOD, cfg);
+ registerTempt(Llama.class, ItemTags.LLAMA_FOOD, cfg);
+ registerTempt(Hoglin.class, ItemTags.HOGLIN_FOOD, cfg);
+ registerTempt(Camel.class, ItemTags.CAMEL_FOOD, cfg);
+ registerTempt(Armadillo.class, ItemTags.ARMADILLO_FOOD, cfg);
+ registerTempt(Sniffer.class, ItemTags.SNIFFER_FOOD, cfg);
+ }
+
+ private static void registerTempt(Class<? extends Entity> clazz, TagKey<Item> tag, MobAiReducerConfig cfg) {
+ TEMPT_CONDITIONS.computeIfAbsent(clazz, k ->
+ TargetingConditions.forNonCombat().ignoreLineOfSight()
+ ).range(cfg.temptRange).selector((entity, level) ->
+ entity.getMainHandItem().is(tag) || (cfg.temptBothHands && entity.getOffhandItem().is(tag))
+ );
+ }
+
+ /**
+ * Optimise a single PathfinderMob. Idempotent safe to call multiple times.
+ * Must be called from the entity's owning Folia region thread.
+ */
+ public static void optimize(PathfinderMob mob) {
+ if (OPTIMIZED.containsKey(mob)) return;
+ MobAiReducerConfig cfg = me.earthme.luminol.LuminolConfig.get().optimizations.mobAiReducer;
+ if (!cfg.enabled) return;
+
+ mob.collides = !cfg.disableCollides;
+ if (cfg.silent) mob.setSilent(true);
+ OPTIMIZED.put(mob, Boolean.TRUE);
+
+ boolean isAnimal = mob instanceof Animal;
+ TargetingConditions temptCond = cfg.temptEnabled ? TEMPT_CONDITIONS.get(mob.getClass()) : null;
+ Set<WrappedGoal> goals = mob.goalSelector.getAvailableGoals();
+
+ synchronized (goals) {
+ Set<WrappedGoal> toRemove = new HashSet<>();
+ Set<WrappedGoal> toAdd = new HashSet<>();
+
+ for (WrappedGoal wg : goals) {
+ Goal goal = wg.getGoal();
+ Class<?> gc = goal.getClass();
+
+ if (cfg.keepDedicated && !gc.getName().contains("ai.goal")) continue;
+
+ if (isAnimal && cfg.breedEnabled && gc == BreedGoal.class) {
+ wg.stop(); toRemove.add(wg);
+ toAdd.add(new WrappedGoal(wg.getPriority(), new OptimizedBreedGoal(cfg, (Animal) mob)));
+ continue;
+ }
+ if (cfg.temptEnabled && gc == TemptGoal.class && temptCond != null) {
+ wg.stop(); toRemove.add(wg);
+ toAdd.add(new WrappedGoal(wg.getPriority(), new OptimizedTemptGoal(cfg, mob, temptCond)));
+ continue;
+ }
+ String name = gc.getSimpleName();
+ if (cfg.aiGoalBlacklist.stream().anyMatch(name::contains) == cfg.aiListMode) {
+ wg.stop(); toRemove.add(wg);
+ }
+ }
+ goals.removeAll(toRemove);
+ goals.addAll(toAdd);
+ }
+ }
+
+ /** Remove dead / invalid entries from the tracking map. */
+ public static void purge() {
+ OPTIMIZED.keySet().removeIf(mob -> !mob.isAlive() || !mob.valid);
+ }
+}
diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java
index 3333333333333333333333333333333333333333..5555555555555555555555555555555555555555 100644
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -1,6 +1,7 @@
// Existing imports omitted for brevity
+import me.earthme.luminol.mobaireducer.MobAiReducerHandler;
public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLevel {
@@ -300,6 +300,16 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
// Folia - region threading - override entity add to apply MobAiReducer
@Override
public boolean addEntity(net.minecraft.world.entity.Entity entity) {
+ // Luminol start - MobAiReducer: optimize PathfinderMobs on spawn/load
+ if (entity instanceof net.minecraft.world.entity.PathfinderMob mob) {
+ if (me.earthme.luminol.LuminolConfig.get().optimizations.mobAiReducer.enabled) {
+ // Delay one tick so the entity's goals are fully registered before we modify them
+ this.getServer().execute(() -> MobAiReducerHandler.optimize(mob));
+ }
+ }
+ // Luminol end - MobAiReducer
return super.addEntity(entity);
}
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
index 0000000000000000000000000000000000000000..6666666666666666666666666666666666666666 100644
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -500,6 +500,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
protected void runServer() {
try {
if (this.initServer()) {
+ // Luminol start - MobAiReducer: register targeting conditions once
+ if (me.earthme.luminol.LuminolConfig.get().optimizations.mobAiReducer.enabled) {
+ me.earthme.luminol.mobaireducer.MobAiReducerHandler.register();
+ }
+ // Luminol end - MobAiReducer
this.nextTickTimeNanos = Util.getNanos();