Added LivingMakeBrainEvent, to allow a consistent way for modders to manipulate entity Brains. #9292

This commit is contained in:
mgaray99 authored and LexManos committed 2023-07-15 12:37:42 -07:00
1 parent 00ad61b4ae
commit 30d0a630d6
6 files changed
+317 -4

No files matched your search

@@ -35,6 +35,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.serialization.Dynamic;
import com.mojang.serialization.Lifecycle;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -77,6 +78,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
@@ -128,6 +130,7 @@ import net.minecraftforge.common.loot.IGlobalLootModifier;
import net.minecraftforge.common.loot.LootModifierManager;
import net.minecraftforge.common.loot.LootTableIdCondition;
import net.minecraftforge.common.util.BlockSnapshot;
import net.minecraftforge.common.util.BrainBuilder;
import net.minecraftforge.common.util.Lazy;
import net.minecraftforge.common.util.MavenVersionStringHelper;
import net.minecraftforge.event.AnvilUpdateEvent;
@@ -160,6 +163,7 @@ import net.minecraftforge.event.entity.living.LivingChangeTargetEvent.ILivingTar
import net.minecraftforge.event.entity.living.LivingFallEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.living.LivingKnockBackEvent;
import net.minecraftforge.event.entity.living.LivingMakeBrainEvent;
import net.minecraftforge.event.entity.living.LivingSwapItemsEvent;
import net.minecraftforge.event.entity.living.LivingUseTotemEvent;
import net.minecraftforge.event.entity.living.LootingLevelEvent;
@@ -244,6 +248,14 @@ public class ForgeHooks
return event;
}
public static Brain<?> onLivingMakeBrain(LivingEntity entity, Brain<?> originalBrain, Dynamic<?> dynamic)
{
BrainBuilder<?> brainBuilder = originalBrain.createBuilder();
LivingMakeBrainEvent event = new LivingMakeBrainEvent(entity, brainBuilder);
MinecraftForge.EVENT_BUS.post(event);
return brainBuilder.makeBrain(dynamic);
}
public static boolean onLivingTick(LivingEntity entity)
{
return MinecraftForge.EVENT_BUS.post(new LivingTickEvent(entity));
@@ -0,0 +1,162 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.util;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Dynamic;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraft.world.entity.ai.behavior.BehaviorControl;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.ai.memory.MemoryStatus;
import net.minecraft.world.entity.ai.sensing.Sensor;
import net.minecraft.world.entity.ai.sensing.SensorType;
import net.minecraft.world.entity.schedule.Activity;
import net.minecraft.world.entity.schedule.Schedule;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.ApiStatus;
/**
* This object is used to encapsulate state found inside a {@link Brain} instance,
* to make it easily accessible for modders to manipulate during {@link net.minecraftforge.event.entity.living.LivingMakeBrainEvent}.<br>
* <br>
* Provided are a variety of getter/setter methods to access and manipulate the encapsulated state.<br>
* <br>
* Methods marked with "INTENDED FOR INTERNAL USE" are only meant to be used inside:
* <ul>
* <li>{@link net.minecraftforge.common.ForgeHooks#onLivingMakeBrain(LivingEntity, Brain, Dynamic)}
* <li>{@link Brain#createBuilder()}
* <li>{@link Brain#copyFromBuilder(BrainBuilder)}
* </ul>
* Of course, nothing egregious will happen should a modder choose to use them for their own purposes.<br>
*/
public class BrainBuilder<E extends LivingEntity> {
private final Collection<MemoryModuleType<?>> memoryTypes = new HashSet<>();
private final Collection<SensorType<? extends Sensor<? super E>>> sensorTypes = new HashSet<>();
private final Map<Integer, Map<Activity, Set<BehaviorControl<? super E>>>> availableBehaviorsByPriority = Maps.newTreeMap();
private Schedule schedule = Schedule.EMPTY;
private final Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> activityRequirements = Maps.newHashMap();
private final Map<Activity, Set<MemoryModuleType<?>>> activityMemoriesToEraseWhenStopped = Maps.newHashMap();
private final Set<Activity> coreActivities = Sets.newHashSet();
private Activity defaultActivity = Activity.IDLE;
public BrainBuilder(Brain<E> ignoredBrain) {}
public Brain.Provider<E> provider() {
return Brain.provider(this.memoryTypes, this.sensorTypes);
}
public Collection<MemoryModuleType<?>> getMemoryTypes() {
return this.memoryTypes;
}
public Collection<SensorType<? extends Sensor<? super E>>> getSensorTypes() {
return this.sensorTypes;
}
public Map<Integer, Map<Activity, Set<BehaviorControl<? super E>>>> getAvailableBehaviorsByPriority() {
return this.availableBehaviorsByPriority;
}
public Schedule getSchedule() {
return this.schedule;
}
public void setSchedule(Schedule schedule) {
this.schedule = schedule;
}
public Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> getActivityRequirements() {
return this.activityRequirements;
}
public Map<Activity, Set<MemoryModuleType<?>>> getActivityMemoriesToEraseWhenStopped() {
return this.activityMemoriesToEraseWhenStopped;
}
public Set<Activity> getCoreActivities() {
return this.coreActivities;
}
public Activity getDefaultActivity() {
return this.defaultActivity;
}
public void setDefaultActivity(Activity defaultActivity) {
this.defaultActivity = defaultActivity;
}
/** You may use this as a helper method for adding a behavior to an Activity by priority to an entity's brain. */
public void addBehaviorToActivityByPriority(Integer priority, Activity activity, BehaviorControl<? super E> behaviorControl) {
this.availableBehaviorsByPriority.computeIfAbsent(priority, (i) -> Maps.newHashMap()).computeIfAbsent(activity, (a) -> Sets.newLinkedHashSet()).add(behaviorControl);
}
/** You may use this as a helper method for adding memory requirements for an Activity to an entity's brain. */
public void addRequirementsToActivity(Activity activity, Collection<Pair<MemoryModuleType<?>, MemoryStatus>> requirements) {
addRequirementsToActivityInternal(this.activityRequirements, activity, requirements);
}
/** You may use this as a helper method for adding a collection of memories to erase when an Activity is stopped to entity's brain. */
public void addMemoriesToEraseWhenActivityStopped(Activity activity, Collection<MemoryModuleType<?>> memories) {
addMemoriesToEraseWhenActivityStoppedInternal(this.activityMemoriesToEraseWhenStopped, activity, memories);
}
//====================================================================================================================//
// Everything below this is just to implement Brain.createBuilder and Brain.copyFromBuilder, hence marked as internal //
//====================================================================================================================//
@ApiStatus.Internal
public void addAvailableBehaviorsByPriorityFrom(Map<Integer, Map<Activity, Set<BehaviorControl<? super E>>>> addFrom) {
addFrom.forEach(((priority, activitySetMap) -> activitySetMap.forEach(((activity, behaviorControls) -> this.availableBehaviorsByPriority.computeIfAbsent(priority, (p) -> Maps.newHashMap()).computeIfAbsent(activity, (a) -> Sets.newLinkedHashSet()).addAll(behaviorControls)))));
}
@ApiStatus.Internal
public void addAvailableBehaviorsByPriorityTo(Map<Integer, Map<Activity, Set<BehaviorControl<? super E>>>> addTo){
this.availableBehaviorsByPriority.forEach(((priority, activitySetMap) -> activitySetMap.forEach(((activity, behaviorControls) -> addTo.computeIfAbsent(priority, (p) -> Maps.newHashMap()).computeIfAbsent(activity, (a) -> Sets.newLinkedHashSet()).addAll(behaviorControls)))));
}
@ApiStatus.Internal
public void addActivityRequirementsFrom(Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> addFrom) {
addFrom.forEach(this::addRequirementsToActivity);
}
@ApiStatus.Internal
public void addActivityRequirementsTo(Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> addTo) {
this.activityRequirements.forEach(((activity, requirements) -> addRequirementsToActivityInternal(addTo, activity, requirements)));
}
@ApiStatus.Internal
public void addActivityMemoriesToEraseWhenStoppedFrom(Map<Activity, Set<MemoryModuleType<?>>> addFrom) {
addFrom.forEach(this::addMemoriesToEraseWhenActivityStopped);
}
@ApiStatus.Internal
public void addActivityMemoriesToEraseWhenStoppedTo(Map<Activity, Set<MemoryModuleType<?>>> addTo) {
this.activityMemoriesToEraseWhenStopped.forEach(((activity, memories) -> addMemoriesToEraseWhenActivityStoppedInternal(addTo, activity, memories)));
}
private static void addMemoriesToEraseWhenActivityStoppedInternal(Map<Activity, Set<MemoryModuleType<?>>> activityMemoriesToEraseWhenStopped, Activity activity, Collection<MemoryModuleType<?>> memories) {
activityMemoriesToEraseWhenStopped.computeIfAbsent(activity, (a) -> Sets.newHashSet()).addAll(memories);
}
private static void addRequirementsToActivityInternal(Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> activityRequirements, Activity activity, Collection<Pair<MemoryModuleType<?>, MemoryStatus>> requirements) {
activityRequirements.computeIfAbsent(activity, (a) -> Sets.newHashSet()).addAll(requirements);
}
@ApiStatus.Internal
public Brain<E> makeBrain(Dynamic<?> dynamic) {
Brain<E> brain = Brain.provider(this.memoryTypes, this.sensorTypes).makeBrain(dynamic);
brain.copyFromBuilder(this);
return brain;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.event.entity.living;
import com.mojang.serialization.Dynamic;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.Brain;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.BrainBuilder;
import net.minecraftforge.eventbus.api.Cancelable;
/**
* LivingMakeBrainEvent is fired whenever a new {@link net.minecraft.world.entity.ai.Brain} instance is created using {@link LivingEntity#makeBrain(Dynamic)}.<br>
* <br>
* To access the internal {@link BrainBuilder}, call {@link LivingMakeBrainEvent#getTypedBrainBuilder(LivingEntity)} using the downcasted {@link LivingEntity} obtained from {@link LivingEvent#getEntity()}.<br>
* <br>
* The BrainBuilder will initially contain all the state found in the original Brain instance.<br>
* <br>
* After this event is posted, a fresh Brain instance will be created using the encapsulated state found in the BrainBuilder
* and replace the previously created Brain instance for the entity.<br>
* <br>
* This event is fired via the {@link ForgeHooks#onLivingMakeBrain(LivingEntity, Brain, Dynamic)}.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
* This event does not have a result. {@link HasResult}<br>
* <br>
* This event is fired on the {@link MinecraftForge#EVENT_BUS}.
**/
public class LivingMakeBrainEvent extends LivingEvent
{
private final BrainBuilder<?> brainBuilder;
public LivingMakeBrainEvent(LivingEntity entity, BrainBuilder<?> brainBuilder)
{
super(entity);
this.brainBuilder = brainBuilder;
}
public <E extends LivingEntity> BrainBuilder<E> getTypedBrainBuilder(E ignoredEntity)
{
return (BrainBuilder<E>) this.brainBuilder;
}
}