Skip to content

Commit

Permalink
Fix feeding trough chunk memory leak, closes #3560
Browse files Browse the repository at this point in the history
This feature worked by going through every ticking entity in the world
and checking if it is a trough. This used to work decently in the past,
but in 1.18 it had to get the TE from the world position.

This is getting replaced with the vanilla point of interest system.
  • Loading branch information
Hubry committed Feb 11, 2022
1 parent c6989f5 commit 7c0786d
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
package vazkii.quark.content.automation.module;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.mojang.datafixers.util.Pair;

import java.util.Objects;

import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.ExperienceOrb;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.ai.village.poi.PoiManager;
import net.minecraft.world.entity.ai.village.poi.PoiType;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.SoundType;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.entity.TickingBlockEntity;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.BabyEntitySpawnEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.LogicalSide;
import vazkii.arl.util.RegistryHelper;
import vazkii.quark.base.module.LoadModule;
import vazkii.quark.base.module.ModuleCategory;
Expand All @@ -45,6 +43,7 @@
@LoadModule(category = ModuleCategory.AUTOMATION, hasSubscriptions = true)
public class FeedingTroughModule extends QuarkModule {
public static BlockEntityType<FeedingTroughBlockEntity> blockEntityType;
public static PoiType feedingTroughPoi;

@Config(description = "How long, in game ticks, between animals being able to eat from the trough")
@Config.Min(1)
Expand All @@ -60,26 +59,6 @@ public class FeedingTroughModule extends QuarkModule {

@Config public static double range = 10;

private static final ThreadLocal<Set<FeedingTroughBlockEntity>> loadedTroughs = ThreadLocal.withInitial(HashSet::new);

@SubscribeEvent
public void buildTroughSet(TickEvent.WorldTickEvent event) {
Set<FeedingTroughBlockEntity> troughs = loadedTroughs.get();
if (event.side == LogicalSide.SERVER) {
if (event.phase == TickEvent.Phase.START) {
breedingOccurred.remove();
List<TickingBlockEntity> tickers = new ArrayList<>(event.world.blockEntityTickers);
for (TickingBlockEntity ticking : tickers) {
BlockEntity tile = event.world.getBlockEntity(ticking.getPos());
if (tile instanceof FeedingTroughBlockEntity)
troughs.add((FeedingTroughBlockEntity) tile);
}
} else {
troughs.clear();
}
}
}

private static final ThreadLocal<Boolean> breedingOccurred = ThreadLocal.withInitial(() -> false);

@SubscribeEvent(priority = EventPriority.LOWEST)
Expand All @@ -96,41 +75,34 @@ public void onOrbSpawn(EntityJoinWorldEvent event) {
}
}

public static Player temptWithTroughs(TemptGoal goal, Player found) {
public static Player temptWithTroughs(TemptGoal goal, Player found, ServerLevel level) {
if (!ModuleLoader.INSTANCE.isModuleEnabled(FeedingTroughModule.class) ||
(found != null && (goal.items.test(found.getMainHandItem()) || goal.items.test(found.getOffhandItem()))))
return found;

if (!(goal.mob instanceof Animal) ||
!((Animal) goal.mob).canFallInLove() ||
((Animal) goal.mob).getAge() != 0)
if (!(goal.mob instanceof Animal animal) ||
!animal.canFallInLove() ||
animal.getAge() != 0)
return found;

double shortestDistanceSq = Double.MAX_VALUE;
BlockPos location = null;
FakePlayer target = null;

Set<FeedingTroughBlockEntity> troughs = loadedTroughs.get();
for (FeedingTroughBlockEntity tile : troughs) {
BlockPos pos = tile.getBlockPos();
double distanceSq = pos.distSqr(goal.mob.position(), true);
if (distanceSq <= range * range && distanceSq < shortestDistanceSq) {
FakePlayer foodHolder = tile.getFoodHolder(goal);
if (foodHolder != null) {
shortestDistanceSq = distanceSq;
target = foodHolder;
location = pos.immutable();
}
}
}

if (target != null) {
Vec3 eyesPos = goal.mob.position().add(0, goal.mob.getEyeHeight(), 0);
Pair<BlockPos, FakePlayer> pair = level.getPoiManager().findAllClosestFirst(
feedingTroughPoi.getPredicate(), p -> p.distSqr(animal.position(), true) <= range * range,
animal.blockPosition(), (int) range, PoiManager.Occupancy.ANY)
.map(pos -> level.getBlockEntity(pos) instanceof FeedingTroughBlockEntity trough ? trough : null)
.filter(Objects::nonNull)
.map(trough -> Pair.of(trough.getBlockPos(), trough.getFoodHolder(goal)))
.filter(p -> p.getSecond() != null)
.findFirst()
.orElse(null);

if (pair != null) {
BlockPos location = pair.getFirst();
Vec3 eyesPos = goal.mob.position().add(0, goal.mob.getEyeHeight(), 0);
Vec3 targetPos = new Vec3(location.getX(), location.getY(), location.getZ()).add(0.5, 0.0625, 0.5);
BlockHitResult ray = goal.mob.level.clip(new ClipContext(eyesPos, targetPos, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, goal.mob));

if (ray.getType() == HitResult.Type.BLOCK && ray.getBlockPos().equals(location))
return target;
return pair.getSecond();
}

return found;
Expand All @@ -142,5 +114,7 @@ public void construct() {
Block.Properties.of(Material.WOOD).strength(0.6F).sound(SoundType.WOOD));
blockEntityType = BlockEntityType.Builder.of(FeedingTroughBlockEntity::new, feedingTrough).build(null);
RegistryHelper.register(blockEntityType, "feeding_trough");
feedingTroughPoi = new PoiType("quark:feeding_trough", PoiType.getBlockStates(feedingTrough), 1, 32);
RegistryHelper.register(feedingTroughPoi, "feeding_trough");
}
}
10 changes: 9 additions & 1 deletion src/main/java/vazkii/quark/mixin/TemptGoalMixin.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package vazkii.quark.mixin;

import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.ai.goal.TemptGoal;
import net.minecraft.world.entity.player.Player;
import vazkii.quark.content.automation.module.FeedingTroughModule;
Expand All @@ -16,8 +19,13 @@ public class TemptGoalMixin {
@Shadow
protected Player player;

@Shadow
@Final
public PathfinderMob mob;

@Inject(method = "canUse", at = @At(value = "FIELD", target = "Lnet/minecraft/world/entity/ai/goal/TemptGoal;player:Lnet/minecraft/world/entity/player/Player;", ordinal = 0, shift = At.Shift.AFTER))
private void findTroughs(CallbackInfoReturnable<Boolean> callbackInfoReturnable) {
player = FeedingTroughModule.temptWithTroughs((TemptGoal) (Object) this, player);
if (mob.level instanceof ServerLevel level)
player = FeedingTroughModule.temptWithTroughs((TemptGoal) (Object) this, player, level);
}
}
1 change: 0 additions & 1 deletion src/main/resources/META-INF/accesstransformer.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public-f net.minecraft.world.inventory.AbstractContainerMenu f_38840_ # windowId
public-f net.minecraft.world.inventory.Slot f_40221_ # yPos
public-f net.minecraft.world.item.Item f_41378_ # containerItem
public net.minecraft.world.item.Item f_41379_ # translationKey
public net.minecraft.world.level.Level f_151512_ # blockEntityTickers
public net.minecraft.world.level.block.Block f_49787_ # translationKey
public net.minecraft.world.level.block.ButtonBlock m_51115_()I # tickRate
public net.minecraft.world.level.block.DispenserBlock f_52661_ # DISPENSE_BEHAVIOR_REGISTRY
Expand Down

0 comments on commit 7c0786d

Please sign in to comment.