minecraft-src/net/minecraft/world/entity/ai/goal/GoalSelector.java
2025-07-04 02:00:41 +03:00

146 lines
4.1 KiB
Java

package net.minecraft.world.entity.ai.goal;
import com.google.common.annotations.VisibleForTesting;
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.util.profiling.Profiler;
import net.minecraft.util.profiling.ProfilerFiller;
public class GoalSelector {
private static final WrappedGoal NO_GOAL = new WrappedGoal(Integer.MAX_VALUE, new Goal() {
@Override
public boolean canUse() {
return false;
}
}) {
@Override
public boolean isRunning() {
return false;
}
};
/**
* Goals currently using a particular flag
*/
private final Map<Goal.Flag, WrappedGoal> lockedFlags = new EnumMap(Goal.Flag.class);
private final Set<WrappedGoal> availableGoals = new ObjectLinkedOpenHashSet<>();
private final EnumSet<Goal.Flag> disabledFlags = EnumSet.noneOf(Goal.Flag.class);
/**
* Add a goal to the GoalSelector with a certain priority. Lower numbers are higher priority.
*/
public void addGoal(int priority, Goal goal) {
this.availableGoals.add(new WrappedGoal(priority, goal));
}
@VisibleForTesting
public void removeAllGoals(Predicate<Goal> filter) {
this.availableGoals.removeIf(wrappedGoal -> filter.test(wrappedGoal.getGoal()));
}
/**
* Remove the goal from the GoalSelector. This must be the same object as the goal you are trying to remove, which may not always be accessible.
*/
public void removeGoal(Goal goal) {
for (WrappedGoal wrappedGoal : this.availableGoals) {
if (wrappedGoal.getGoal() == goal && wrappedGoal.isRunning()) {
wrappedGoal.stop();
}
}
this.availableGoals.removeIf(wrappedGoalx -> wrappedGoalx.getGoal() == goal);
}
private static boolean goalContainsAnyFlags(WrappedGoal goal, EnumSet<Goal.Flag> flag) {
for (Goal.Flag flag2 : goal.getFlags()) {
if (flag.contains(flag2)) {
return true;
}
}
return false;
}
private static boolean goalCanBeReplacedForAllFlags(WrappedGoal goal, Map<Goal.Flag, WrappedGoal> flag) {
for (Goal.Flag flag2 : goal.getFlags()) {
if (!((WrappedGoal)flag.getOrDefault(flag2, NO_GOAL)).canBeReplacedBy(goal)) {
return false;
}
}
return true;
}
/**
* Ticks every goal in the selector.
* Attempts to start each goal based on if it can be used, or stop it if it can't.
*/
public void tick() {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("goalCleanup");
for (WrappedGoal wrappedGoal : this.availableGoals) {
if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.disabledFlags) || !wrappedGoal.canContinueToUse())) {
wrappedGoal.stop();
}
}
this.lockedFlags.entrySet().removeIf(entry -> !((WrappedGoal)entry.getValue()).isRunning());
profilerFiller.pop();
profilerFiller.push("goalUpdate");
for (WrappedGoal wrappedGoalx : this.availableGoals) {
if (!wrappedGoalx.isRunning()
&& !goalContainsAnyFlags(wrappedGoalx, this.disabledFlags)
&& goalCanBeReplacedForAllFlags(wrappedGoalx, this.lockedFlags)
&& wrappedGoalx.canUse()) {
for (Goal.Flag flag : wrappedGoalx.getFlags()) {
WrappedGoal wrappedGoal2 = (WrappedGoal)this.lockedFlags.getOrDefault(flag, NO_GOAL);
wrappedGoal2.stop();
this.lockedFlags.put(flag, wrappedGoalx);
}
wrappedGoalx.start();
}
}
profilerFiller.pop();
this.tickRunningGoals(true);
}
public void tickRunningGoals(boolean tickAllRunning) {
ProfilerFiller profilerFiller = Profiler.get();
profilerFiller.push("goalTick");
for (WrappedGoal wrappedGoal : this.availableGoals) {
if (wrappedGoal.isRunning() && (tickAllRunning || wrappedGoal.requiresUpdateEveryTick())) {
wrappedGoal.tick();
}
}
profilerFiller.pop();
}
public Set<WrappedGoal> getAvailableGoals() {
return this.availableGoals;
}
public void disableControlFlag(Goal.Flag flag) {
this.disabledFlags.add(flag);
}
public void enableControlFlag(Goal.Flag flag) {
this.disabledFlags.remove(flag);
}
public void setControlFlag(Goal.Flag flag, boolean enabled) {
if (enabled) {
this.enableControlFlag(flag);
} else {
this.disableControlFlag(flag);
}
}
}