we ball
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
group=net.gensokyoreimagined.nitori
|
||||
version=1.2-SNAPSHOT
|
||||
version=1.3-SNAPSHOT
|
||||
description=Converting patches into mixins, for the Ignite Framework
|
||||
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.gensokyoreimagined.nitori.common.ai.pathing;
|
||||
|
||||
public class BlockStatePathingCache {
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package net.gensokyoreimagined.nitori.common.ai.pathing;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.BlockCountingSection;
|
||||
import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
|
||||
import net.gensokyoreimagined.nitori.common.util.Pos;
|
||||
import net.gensokyoreimagined.nitori.common.world.ChunkView;
|
||||
import net.gensokyoreimagined.nitori.common.world.WorldHelper;
|
||||
import me.jellysquid.mods.lithium.mixin.ai.pathing.PathContextAccessor;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
|
||||
import net.minecraft.world.level.pathfinder.BinaryHeap;
|
||||
import net.minecraft.entity.ai.pathing.PathNodeType;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.BlockView;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
|
||||
public abstract class PathNodeCache {
|
||||
private static boolean isChunkSectionDangerousNeighbor(ChunkSection section) {
|
||||
return section.getBlockStateContainer()
|
||||
.hasAny(state -> getNeighborPathNodeType(state) != PathNodeType.OPEN);
|
||||
}
|
||||
|
||||
public static PathNodeType getPathNodeType(BlockState state) {
|
||||
return ((BlockStatePathingCache) state).lithium$getPathNodeType();
|
||||
}
|
||||
|
||||
public static PathNodeType getNeighborPathNodeType(BlockBehaviour.AbstractBlockState state) {
|
||||
return ((BlockStatePathingCache) state).lithium$getNeighborPathNodeType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a chunk section is free of dangers. This makes use of a caching layer to greatly
|
||||
* accelerate neighbor danger checks when path-finding.
|
||||
*
|
||||
* @param section The chunk section to test for dangers
|
||||
* @return True if this neighboring section is free of any dangers, otherwise false if it could
|
||||
* potentially contain dangers
|
||||
*/
|
||||
public static boolean isSectionSafeAsNeighbor(ChunkSection section) {
|
||||
// Empty sections can never contribute a danger
|
||||
if (section.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (BlockStateFlags.ENABLED) {
|
||||
return !((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.PATH_NOT_OPEN);
|
||||
}
|
||||
return !isChunkSectionDangerousNeighbor(section);
|
||||
}
|
||||
|
||||
|
||||
public static PathNodeType getNodeTypeFromNeighbors(BinaryHeap context, int x, int y, int z, PathNodeType fallback) {
|
||||
BlockView world = context.getWorld();
|
||||
|
||||
ChunkSection section = null;
|
||||
|
||||
// Check that all the block's neighbors are within the same chunk column. If so, we can isolate all our block
|
||||
// reads to just one chunk and avoid hits against the server chunk manager.
|
||||
if (world instanceof ChunkView chunkView && WorldHelper.areNeighborsWithinSameChunkSection(x, y, z)) {
|
||||
// If the y-coordinate is within bounds, we can cache the chunk section. Otherwise, the if statement to check
|
||||
// if the cached chunk section was initialized will early-exit.
|
||||
if (!world.isOutOfHeightLimit(y)) {
|
||||
Chunk chunk = chunkView.lithium$getLoadedChunk(Pos.ChunkCoord.fromBlockCoord(x), Pos.ChunkCoord.fromBlockCoord(z));
|
||||
|
||||
// If the chunk is absent, the cached section above will remain null, as there is no chunk section anyway.
|
||||
// An empty chunk or section will never pose any danger sources, which will be caught later.
|
||||
if (chunk != null) {
|
||||
section = chunk.getSectionArray()[Pos.SectionYIndex.fromBlockCoord(world, y)];
|
||||
}
|
||||
}
|
||||
|
||||
// If we can guarantee that blocks won't be modified while the cache is active, try to see if the chunk
|
||||
// section is empty or contains any dangerous blocks within the palette. If not, we can assume any checks
|
||||
// against this chunk section will always fail, allowing us to fast-exit.
|
||||
if (section == null || PathNodeCache.isSectionSafeAsNeighbor(section)) {
|
||||
return fallback; //TODO side effects of vanilla's path node caching
|
||||
}
|
||||
}
|
||||
|
||||
int xStart = x - 1;
|
||||
int yStart = y - 1;
|
||||
int zStart = z - 1;
|
||||
|
||||
int xEnd = x + 1;
|
||||
int yEnd = y + 1;
|
||||
int zEnd = z + 1;
|
||||
|
||||
// Vanilla iteration order is XYZ
|
||||
for (int adjX = xStart; adjX <= xEnd; adjX++) {
|
||||
for (int adjY = yStart; adjY <= yEnd; adjY++) {
|
||||
for (int adjZ = zStart; adjZ <= zEnd; adjZ++) {
|
||||
// Skip the vertical column of the origin block
|
||||
if (adjX == x && adjZ == z) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockState state;
|
||||
|
||||
// If we're not accessing blocks outside a given section, we can greatly accelerate block state
|
||||
// retrieval by calling upon the cached chunk directly.
|
||||
if (section != null) {
|
||||
state = section.getBlockState(adjX & 15, adjY & 15, adjZ & 15);
|
||||
} else {
|
||||
BlockPos.Mutable pos = ((PathContextAccessor) context).getLastNodePos().set(adjX, adjY, adjZ);
|
||||
state = world.getBlockState(pos);
|
||||
}
|
||||
|
||||
if (state.isAir()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PathNodeType neighborType = PathNodeCache.getNeighborPathNodeType(state);
|
||||
|
||||
if (neighborType == null) { //Here null means that no path node type is cached (uninitialized or dynamic)
|
||||
//Passing null as previous node type to the method signals to other lithium mixins that we only want the neighbor behavior of this block and not its neighbors
|
||||
neighborType = WalkNodeEvaluator.getNodeTypeFromNeighbors(context, adjX + 1, adjY + 1, adjZ + 1, null);
|
||||
//Here null means that the path node type is not changed by the block!
|
||||
if (neighborType == null) {
|
||||
neighborType = PathNodeType.OPEN;
|
||||
}
|
||||
}
|
||||
if (neighborType != PathNodeType.OPEN) {
|
||||
return neighborType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.gensokyoreimagined.nitori.common.block;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public interface BlockListeningSection {
|
||||
|
||||
void lithium$addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker, long sectionPos, Level world);
|
||||
|
||||
void lithium$removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker);
|
||||
|
||||
void lithium$invalidateListeningSection(SectionPos sectionPos);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package net.gensokyoreimagined.nitori.common.block;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2BooleanArrayMap;
|
||||
import net.gensokyoreimagined.nitori.common.ai.pathing.BlockStatePathingCache;
|
||||
import net.gensokyoreimagined.nitori.common.ai.pathing.PathNodeCache;
|
||||
import net.gensokyoreimagined.nitori.common.entity.FluidCachingEntity;
|
||||
import net.gensokyoreimagined.nitori.common.reflection.ReflectionUtil;
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.ai.pathing.PathNodeType;
|
||||
import net.minecraft.registry.tag.FluidTags;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BlockStateFlags {
|
||||
public static final boolean ENABLED = BlockCountingSection.class.isAssignableFrom(ChunkSection.class);
|
||||
|
||||
public static final int NUM_LISTENING_FLAGS;
|
||||
public static final ListeningBlockStatePredicate[] LISTENING_FLAGS;
|
||||
public static final int LISTENING_MASK_OR;
|
||||
|
||||
//Listening Flag
|
||||
public static final ListeningBlockStatePredicate ANY;
|
||||
|
||||
public static final int NUM_TRACKED_FLAGS;
|
||||
public static final TrackedBlockStatePredicate[] TRACKED_FLAGS;
|
||||
|
||||
//Counting flags
|
||||
public static final TrackedBlockStatePredicate OVERSIZED_SHAPE;
|
||||
public static final TrackedBlockStatePredicate PATH_NOT_OPEN;
|
||||
public static final TrackedBlockStatePredicate WATER;
|
||||
public static final TrackedBlockStatePredicate LAVA;
|
||||
|
||||
public static final TrackedBlockStatePredicate[] FLAGS;
|
||||
|
||||
//Non counting flags
|
||||
public static final TrackedBlockStatePredicate ENTITY_TOUCHABLE;
|
||||
|
||||
static {
|
||||
Reference2BooleanArrayMap<ListeningBlockStatePredicate> listeningFlags = new Reference2BooleanArrayMap<>();
|
||||
|
||||
ANY = new ListeningBlockStatePredicate(listeningFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
//false -> we listen to changes of all blocks that pass the predicate test.
|
||||
//true -> we only listen to changes of the predicate test result
|
||||
listeningFlags.put(ANY, false);
|
||||
|
||||
NUM_LISTENING_FLAGS = listeningFlags.size();
|
||||
int listenMaskOR = 0;
|
||||
int iteration = 0;
|
||||
for (var entry : listeningFlags.reference2BooleanEntrySet()) {
|
||||
boolean listenOnlyXOR = entry.getBooleanValue();
|
||||
listenMaskOR |= listenOnlyXOR ? 0 : 1 << iteration;
|
||||
}
|
||||
LISTENING_MASK_OR = listenMaskOR;
|
||||
LISTENING_FLAGS = listeningFlags.keySet().toArray(new ListeningBlockStatePredicate[NUM_LISTENING_FLAGS]);
|
||||
|
||||
|
||||
ArrayList<TrackedBlockStatePredicate> countingFlags = new ArrayList<>(listeningFlags.keySet());
|
||||
|
||||
OVERSIZED_SHAPE = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return operand.exceedsCube();
|
||||
}
|
||||
};
|
||||
countingFlags.add(OVERSIZED_SHAPE);
|
||||
|
||||
if (FluidCachingEntity.class.isAssignableFrom(Entity.class)) {
|
||||
WATER = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return operand.getFluidState().getFluid().isIn(FluidTags.WATER);
|
||||
}
|
||||
};
|
||||
countingFlags.add(WATER);
|
||||
|
||||
LAVA = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return operand.getFluidState().getFluid().isIn(FluidTags.LAVA);
|
||||
}
|
||||
};
|
||||
countingFlags.add(LAVA);
|
||||
} else {
|
||||
WATER = null;
|
||||
LAVA = null;
|
||||
}
|
||||
|
||||
if (BlockStatePathingCache.class.isAssignableFrom(AbstractBlock.AbstractBlockState.class)) {
|
||||
PATH_NOT_OPEN = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return PathNodeCache.getNeighborPathNodeType(operand) != PathNodeType.OPEN;
|
||||
}
|
||||
};
|
||||
countingFlags.add(PATH_NOT_OPEN);
|
||||
} else {
|
||||
PATH_NOT_OPEN = null;
|
||||
}
|
||||
|
||||
NUM_TRACKED_FLAGS = countingFlags.size();
|
||||
TRACKED_FLAGS = countingFlags.toArray(new TrackedBlockStatePredicate[NUM_TRACKED_FLAGS]);
|
||||
|
||||
ArrayList<TrackedBlockStatePredicate> flags = new ArrayList<>(countingFlags);
|
||||
|
||||
ENTITY_TOUCHABLE = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||
@Override
|
||||
public boolean test(BlockState operand) {
|
||||
return ReflectionUtil.isBlockStateEntityTouchable(operand);
|
||||
}
|
||||
};
|
||||
flags.add(ENTITY_TOUCHABLE);
|
||||
|
||||
FLAGS = flags.toArray(new TrackedBlockStatePredicate[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.gensokyoreimagined.nitori.common.block;
|
||||
|
||||
public abstract class ListeningBlockStatePredicate extends TrackedBlockStatePredicate {
|
||||
public static int LISTENING_MASK;
|
||||
|
||||
protected ListeningBlockStatePredicate(int index) {
|
||||
super(index);
|
||||
LISTENING_MASK |= (1 << this.getIndex());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.gensokyoreimagined.nitori.common.block.entity;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record SleepUntilTimeBlockEntityTickInvoker(BlockEntity sleepingBlockEntity, long sleepUntilTickExclusive,
|
||||
TickingBlockEntity delegate) implements TickingBlockEntity {
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
//noinspection ConstantConditions
|
||||
long tickTime = this.sleepingBlockEntity.getLevel().getGameTime();
|
||||
if (tickTime >= this.sleepUntilTickExclusive) {
|
||||
((SleepingBlockEntity) this.sleepingBlockEntity).setTicker(this.delegate);
|
||||
this.delegate.tick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemoved() {
|
||||
return this.sleepingBlockEntity.isRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BlockPos getPos() {
|
||||
return this.sleepingBlockEntity.getBlockPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
//noinspection ConstantConditions
|
||||
return BlockEntityType.getKey(this.sleepingBlockEntity.getType()).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package net.gensokyoreimagined.nitori.common.block.entity;
|
||||
|
||||
import net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
|
||||
public interface SleepingBlockEntity {
|
||||
TickingBlockEntity SLEEPING_BLOCK_ENTITY_TICKER = new TickingBlockEntity() {
|
||||
public void tick() {
|
||||
}
|
||||
|
||||
public boolean isRemoved() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public BlockPos getPos() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return "<lithium_sleeping>";
|
||||
}
|
||||
};
|
||||
|
||||
WrappedBlockEntityTickInvokerAccessor lithium$getTickWrapper();
|
||||
|
||||
void lithium$setTickWrapper(WrappedBlockEntityTickInvokerAccessor tickWrapper);
|
||||
|
||||
TickingBlockEntity lithium$getSleepingTicker();
|
||||
|
||||
void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker);
|
||||
|
||||
default boolean nitori$startSleeping() {
|
||||
if (this.isSleeping()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
|
||||
if (tickWrapper == null) {
|
||||
return false;
|
||||
}
|
||||
this.lithium$setSleepingTicker(tickWrapper.getTicker());
|
||||
tickWrapper.callRebind(SleepingBlockEntity.SLEEPING_BLOCK_ENTITY_TICKER);
|
||||
return true;
|
||||
}
|
||||
|
||||
default void sleepOnlyCurrentTick() {
|
||||
TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker();
|
||||
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
|
||||
if (sleepingTicker == null) {
|
||||
sleepingTicker = tickWrapper.getTicker();
|
||||
}
|
||||
Level world = ((BlockEntity) this).getLevel();
|
||||
tickWrapper.callRebind(new SleepUntilTimeBlockEntityTickInvoker((BlockEntity) this, world.getGameTime() + 1, sleepingTicker));
|
||||
this.lithium$setSleepingTicker(null);
|
||||
}
|
||||
|
||||
default void wakeUpNow() {
|
||||
TickingBlockEntity sleepingTicker = this.lithium$getSleepingTicker();
|
||||
if (sleepingTicker == null) {
|
||||
return;
|
||||
}
|
||||
this.setTicker(sleepingTicker);
|
||||
this.lithium$setSleepingTicker(null);
|
||||
}
|
||||
|
||||
default void setTicker(TickingBlockEntity delegate) {
|
||||
WrappedBlockEntityTickInvokerAccessor tickWrapper = this.lithium$getTickWrapper();
|
||||
if (tickWrapper == null) {
|
||||
return;
|
||||
}
|
||||
tickWrapper.callRebind(delegate);
|
||||
}
|
||||
|
||||
default boolean isSleeping() {
|
||||
return this.lithium$getSleepingTicker() != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.gensokyoreimagined.nitori.common.block.entity.movement_tracker;
|
||||
|
||||
public class SectionedEntityMovementTracker {
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package net.gensokyoreimagined.nitori.common.entity.block_tracking;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
|
||||
import net.gensokyoreimagined.nitori.common.block.BlockListeningSection;
|
||||
import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
|
||||
import net.gensokyoreimagined.nitori.common.block.ListeningBlockStatePredicate;
|
||||
import net.gensokyoreimagined.nitori.common.util.Pos;
|
||||
import net.gensokyoreimagined.nitori.common.world.LithiumData;
|
||||
import net.gensokyoreimagined.nitori.common.world.chunk.ChunkStatusTracker;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class ChunkSectionChangeCallback {
|
||||
private final ArrayList<SectionedBlockChangeTracker>[] trackers;
|
||||
private short listeningMask;
|
||||
|
||||
static {
|
||||
if (BlockListeningSection.class.isAssignableFrom(LevelChunkSection.class)) {
|
||||
ChunkStatusTracker.registerUnloadCallback((serverWorld, chunkPos) -> {
|
||||
Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> changeCallbacks = ((LithiumData) serverWorld).lithium$getData().chunkSectionChangeCallbacks();
|
||||
int x = chunkPos.x;
|
||||
int z = chunkPos.z;
|
||||
for (int y = Pos.SectionYCoord.getMinYSection(serverWorld); y <= Pos.SectionYCoord.getMaxYSectionInclusive(serverWorld); y++) {
|
||||
SectionPos chunkSectionPos = SectionPos.of(x, y, z);
|
||||
ChunkSectionChangeCallback chunkSectionChangeCallback = changeCallbacks.remove(chunkSectionPos.asLong());
|
||||
if (chunkSectionChangeCallback != null) {
|
||||
chunkSectionChangeCallback.onChunkSectionInvalidated(chunkSectionPos);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ChunkSectionChangeCallback() {
|
||||
//noinspection unchecked
|
||||
this.trackers = new ArrayList[BlockStateFlags.NUM_LISTENING_FLAGS];
|
||||
this.listeningMask = 0;
|
||||
}
|
||||
|
||||
public static ChunkSectionChangeCallback create(long sectionPos, Level world) {
|
||||
ChunkSectionChangeCallback chunkSectionChangeCallback = new ChunkSectionChangeCallback();
|
||||
Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> changeCallbacks = ((LithiumData) world).lithium$getData().chunkSectionChangeCallbacks();
|
||||
ChunkSectionChangeCallback previous = changeCallbacks.put(sectionPos, chunkSectionChangeCallback);
|
||||
if (previous != null) {
|
||||
previous.onChunkSectionInvalidated(SectionPos.from(sectionPos));
|
||||
}
|
||||
return chunkSectionChangeCallback;
|
||||
}
|
||||
|
||||
public short onBlockChange(int blockGroupIndex, BlockListeningSection section) {
|
||||
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
|
||||
this.trackers[blockGroupIndex] = null;
|
||||
if (sectionedBlockChangeTrackers != null) {
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < sectionedBlockChangeTrackers.size(); i++) {
|
||||
sectionedBlockChangeTrackers.get(i).setChanged(section);
|
||||
}
|
||||
}
|
||||
this.listeningMask &= (short) ~(1 << blockGroupIndex);
|
||||
|
||||
return this.listeningMask;
|
||||
}
|
||||
|
||||
public short addTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
|
||||
int blockGroupIndex = blockGroup.getIndex();
|
||||
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
|
||||
if (sectionedBlockChangeTrackers == null) {
|
||||
this.trackers[blockGroupIndex] = (sectionedBlockChangeTrackers = new ArrayList<>());
|
||||
}
|
||||
sectionedBlockChangeTrackers.add(tracker);
|
||||
|
||||
this.listeningMask |= (short) (1 << blockGroupIndex);
|
||||
return this.listeningMask;
|
||||
}
|
||||
|
||||
public short removeTracker(SectionedBlockChangeTracker tracker, ListeningBlockStatePredicate blockGroup) {
|
||||
int blockGroupIndex = blockGroup.getIndex();
|
||||
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[blockGroupIndex];
|
||||
if (sectionedBlockChangeTrackers != null) {
|
||||
sectionedBlockChangeTrackers.remove(tracker);
|
||||
if (sectionedBlockChangeTrackers.isEmpty()) {
|
||||
this.listeningMask &= (short) ~(1 << blockGroup.getIndex());
|
||||
}
|
||||
}
|
||||
return this.listeningMask;
|
||||
}
|
||||
|
||||
public void onChunkSectionInvalidated(SectionPos sectionPos) {
|
||||
for (int flagIndex = 0; flagIndex < this.trackers.length; flagIndex++) {
|
||||
ArrayList<SectionedBlockChangeTracker> sectionedBlockChangeTrackers = this.trackers[flagIndex];
|
||||
this.trackers[flagIndex] = null;
|
||||
if (sectionedBlockChangeTrackers != null) {
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for (int i = 0; i < sectionedBlockChangeTrackers.size(); i++) {
|
||||
sectionedBlockChangeTrackers.get(i).onChunkSectionInvalidated(sectionPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.listeningMask = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package net.gensokyoreimagined.nitori.common.entity.block_tracking;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.BlockListeningSection;
|
||||
import net.gensokyoreimagined.nitori.common.block.ListeningBlockStatePredicate;
|
||||
import net.gensokyoreimagined.nitori.common.util.Pos;
|
||||
import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInterner;
|
||||
import net.gensokyoreimagined.nitori.common.util.tuples.WorldSectionBox;
|
||||
import net.gensokyoreimagined.nitori.common.world.LithiumData;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
public class SectionedBlockChangeTracker {
|
||||
public final WorldSectionBox trackedWorldSections;
|
||||
public final ListeningBlockStatePredicate blockGroup;
|
||||
|
||||
private long maxChangeTime;
|
||||
|
||||
private int timesRegistered;
|
||||
//Some sections may not exist / be unloaded. We have to be aware of those. //TODO Invalidation when sections / chunks unload (but the entity does not (?), not sure whether this is possible)
|
||||
boolean isListeningToAll = false;
|
||||
private ArrayList<SectionPos> sectionsNotListeningTo = null;
|
||||
private ArrayList<BlockListeningSection> sectionsUnsubscribed = null;
|
||||
|
||||
public SectionedBlockChangeTracker(WorldSectionBox trackedWorldSections, ListeningBlockStatePredicate blockGroup) {
|
||||
this.trackedWorldSections = trackedWorldSections;
|
||||
this.blockGroup = blockGroup;
|
||||
|
||||
this.maxChangeTime = 0;
|
||||
}
|
||||
|
||||
public boolean matchesMovedBox(AABB box) {
|
||||
return this.trackedWorldSections.matchesRelevantBlocksBox(box);
|
||||
}
|
||||
|
||||
public static SectionedBlockChangeTracker registerAt(Level world, AABB entityBoundingBox, ListeningBlockStatePredicate blockGroup) {
|
||||
WorldSectionBox worldSectionBox = WorldSectionBox.relevantExpandedBlocksBox(world, entityBoundingBox);
|
||||
SectionedBlockChangeTracker tracker = new SectionedBlockChangeTracker(worldSectionBox, blockGroup);
|
||||
|
||||
LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers = ((LithiumData) world).lithium$getData().blockChangeTrackers();
|
||||
tracker = blockChangeTrackers.getCanonical(tracker);
|
||||
|
||||
tracker.register();
|
||||
return tracker;
|
||||
}
|
||||
|
||||
long getWorldTime() {
|
||||
return this.trackedWorldSections.world().getGameTime();
|
||||
}
|
||||
|
||||
public void register() {
|
||||
if (this.timesRegistered == 0) {
|
||||
WorldSectionBox trackedSections = this.trackedWorldSections;
|
||||
for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
|
||||
for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
|
||||
Level world = trackedSections.world();
|
||||
ChunkAccess chunk = world.getChunk(x, z, ChunkStatus.FULL, false);
|
||||
LevelChunkSection[] sectionArray = chunk == null ? null : chunk.getSections();
|
||||
for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
|
||||
if (Pos.SectionYCoord.getMinYSection(world) > y || Pos.SectionYCoord.getMaxYSectionExclusive(world) <= y) {
|
||||
continue;
|
||||
}
|
||||
SectionPos sectionPos = SectionPos.of(x, y, z);
|
||||
if (sectionArray == null) {
|
||||
if (this.sectionsNotListeningTo == null) {
|
||||
this.sectionsNotListeningTo = new ArrayList<>();
|
||||
}
|
||||
this.sectionsNotListeningTo.add(sectionPos);
|
||||
continue;
|
||||
}
|
||||
LevelChunkSection section = sectionArray[Pos.SectionYIndex.fromSectionCoord(world, y)];
|
||||
|
||||
BlockListeningSection blockListeningSection = (BlockListeningSection) section;
|
||||
blockListeningSection.lithium$addToCallback(this.blockGroup, this, SectionPos.asLong(x, y, z), world);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.isListeningToAll = (this.sectionsNotListeningTo == null || this.sectionsNotListeningTo.isEmpty())
|
||||
&& (this.sectionsUnsubscribed == null || this.sectionsUnsubscribed.isEmpty());
|
||||
this.setChanged(this.getWorldTime());
|
||||
}
|
||||
this.timesRegistered++;
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
if (--this.timesRegistered > 0) {
|
||||
return;
|
||||
}
|
||||
WorldSectionBox trackedSections = this.trackedWorldSections;
|
||||
Level world = trackedSections.world();
|
||||
for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
|
||||
for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
|
||||
ChunkAccess chunk = world.getChunk(x, z, ChunkStatus.FULL, false);
|
||||
LevelChunkSection[] sectionArray = chunk == null ? null : chunk.getSections();
|
||||
for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
|
||||
|
||||
if (sectionArray == null) {
|
||||
continue;
|
||||
}
|
||||
if (Pos.SectionYCoord.getMinYSection(world) > y || Pos.SectionYCoord.getMaxYSectionExclusive(world) <= y) {
|
||||
continue;
|
||||
}
|
||||
LevelChunkSection section = sectionArray[Pos.SectionYIndex.fromSectionCoord(world, y)];
|
||||
|
||||
BlockListeningSection blockListeningSection = (BlockListeningSection) section;
|
||||
blockListeningSection.lithium$removeFromCallback(this.blockGroup, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.sectionsNotListeningTo = null;
|
||||
LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers = ((LithiumData) world).lithium$getData().blockChangeTrackers();
|
||||
blockChangeTrackers.deleteCanonical(this);
|
||||
}
|
||||
|
||||
public void listenToAllSections() {
|
||||
boolean changed = false;
|
||||
ArrayList<SectionPos> notListeningTo = this.sectionsNotListeningTo;
|
||||
if (notListeningTo != null) {
|
||||
for (int i = notListeningTo.size() - 1; i >= 0; i--) {
|
||||
changed = true;
|
||||
SectionPos chunkSectionPos = notListeningTo.get(i);
|
||||
Level world = this.trackedWorldSections.world();
|
||||
ChunkAccess chunk = world.getChunk(chunkSectionPos.getX(), chunkSectionPos.getZ(), ChunkStatus.FULL, false);
|
||||
if (chunk != null) {
|
||||
notListeningTo.remove(i);
|
||||
} else {
|
||||
//Chunk not loaded, cannot listen to all sections.
|
||||
return;
|
||||
}
|
||||
LevelChunkSection section = chunk.getSections()[Pos.SectionYIndex.fromSectionCoord(world, chunkSectionPos.getY())];
|
||||
BlockListeningSection blockListeningSection = (BlockListeningSection) section;
|
||||
blockListeningSection.lithium$addToCallback(this.blockGroup, this, chunkSectionPos.asLong(), world);
|
||||
}
|
||||
}
|
||||
if (this.sectionsUnsubscribed != null) {
|
||||
ArrayList<BlockListeningSection> unsubscribed = this.sectionsUnsubscribed;
|
||||
for (int i = unsubscribed.size() - 1; i >= 0; i--) {
|
||||
changed = true;
|
||||
BlockListeningSection blockListeningSection = unsubscribed.remove(i);
|
||||
blockListeningSection.lithium$addToCallback(this.blockGroup, this, Long.MIN_VALUE, null);
|
||||
}
|
||||
}
|
||||
this.isListeningToAll = true;
|
||||
if (changed) {
|
||||
this.setChanged(this.getWorldTime());
|
||||
}
|
||||
}
|
||||
|
||||
public void setChanged(BlockListeningSection section) {
|
||||
if (this.sectionsUnsubscribed == null) {
|
||||
this.sectionsUnsubscribed = new ArrayList<>();
|
||||
}
|
||||
this.sectionsUnsubscribed.add(section);
|
||||
this.setChanged(this.getWorldTime());
|
||||
this.isListeningToAll = false;
|
||||
}
|
||||
|
||||
public void setChanged(long atTime) {
|
||||
if (atTime > this.maxChangeTime) {
|
||||
this.maxChangeTime = atTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to quickly check whether any relevant blocks changed inside the relevant chunk sections after
|
||||
* the last test.
|
||||
*
|
||||
* @param lastCheckedTime time of the last interaction attempt
|
||||
* @return whether any relevant entity moved in the tracked area
|
||||
*/
|
||||
public boolean isUnchangedSince(long lastCheckedTime) {
|
||||
if (lastCheckedTime <= this.maxChangeTime) {
|
||||
return false;
|
||||
}
|
||||
if (!this.isListeningToAll) {
|
||||
this.listenToAllSections();
|
||||
return this.isListeningToAll && lastCheckedTime > this.maxChangeTime;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//Do not modify, used for deduplication of instances
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (SectionedBlockChangeTracker) obj;
|
||||
return Objects.equals(this.trackedWorldSections, that.trackedWorldSections) &&
|
||||
Objects.equals(this.blockGroup, that.blockGroup);
|
||||
}
|
||||
//Do not modify, used for deduplication of instances
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getClass().hashCode() ^ this.trackedWorldSections.hashCode() ^ this.blockGroup.hashCode();
|
||||
}
|
||||
|
||||
public void onChunkSectionInvalidated(SectionPos sectionPos) {
|
||||
if (this.sectionsNotListeningTo == null) {
|
||||
this.sectionsNotListeningTo = new ArrayList<>();
|
||||
}
|
||||
this.sectionsNotListeningTo.add(sectionPos);
|
||||
this.setChanged(this.getWorldTime());
|
||||
this.isListeningToAll = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.deduplication;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
||||
|
||||
public class LithiumInterner<T> {
|
||||
private final ObjectOpenHashSet<T> canonicalStorage = new ObjectOpenHashSet<>();
|
||||
|
||||
public <S extends T> S getCanonical(S value) {
|
||||
//noinspection unchecked
|
||||
return (S) this.canonicalStorage.addOrGet(value);
|
||||
}
|
||||
|
||||
public void deleteCanonical(T value) {
|
||||
this.canonicalStorage.remove(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.tuples;
|
||||
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
//Y values use coordinates, not indices (y=0 -> chunkY=0)
|
||||
//upper bounds are EXCLUSIVE
|
||||
public record WorldSectionBox(Level world, int chunkX1, int chunkY1, int chunkZ1, int chunkX2, int chunkY2,
|
||||
int chunkZ2) {
|
||||
public static WorldSectionBox entityAccessBox(Level world, AABB box) {
|
||||
int minX = SectionPos.blockToSectionCoord(box.minX - 2.0D);
|
||||
int minY = SectionPos.blockToSectionCoord(box.minY - 4.0D);
|
||||
int minZ = SectionPos.blockToSectionCoord(box.minZ - 2.0D);
|
||||
int maxX = SectionPos.blockToSectionCoord(box.maxX + 2.0D) + 1;
|
||||
int maxY = SectionPos.blockToSectionCoord(box.maxY) + 1;
|
||||
int maxZ = SectionPos.blockToSectionCoord(box.maxZ + 2.0D) + 1;
|
||||
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
//Relevant block box: Entity hitbox expanded to all blocks it touches. Then expand the resulting box by 1 block in each direction.
|
||||
//Include all chunk sections that contain blocks inside the expanded box.
|
||||
public static WorldSectionBox relevantExpandedBlocksBox(Level world, AABB box) {
|
||||
int minX = SectionPos.blockToSectionCoord(Mth.floor(box.minX) - 1);
|
||||
int minY = SectionPos.blockToSectionCoord(Mth.floor(box.minY) - 1);
|
||||
int minZ = SectionPos.blockToSectionCoord(Mth.floor(box.minZ) - 1);
|
||||
int maxX = SectionPos.blockToSectionCoord(Mth.floor(box.maxX) + 1) + 1;
|
||||
int maxY = SectionPos.blockToSectionCoord(Mth.floor(box.maxY) + 1) + 1;
|
||||
int maxZ = SectionPos.blockToSectionCoord(Mth.floor(box.maxZ) + 1) + 1;
|
||||
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
//Like relevant blocks, but not expanded, because fluids never exceed the 1x1x1 volume of a block
|
||||
public static WorldSectionBox relevantFluidBox(Level world, AABB box) {
|
||||
int minX = SectionPos.blockToSectionCoord(Mth.floor(box.minX));
|
||||
int minY = SectionPos.blockToSectionCoord(Mth.floor(box.minY));
|
||||
int minZ = SectionPos.blockToSectionCoord(Mth.floor(box.minZ));
|
||||
int maxX = SectionPos.blockToSectionCoord(Mth.floor(box.maxX)) + 1;
|
||||
int maxY = SectionPos.blockToSectionCoord(Mth.floor(box.maxY)) + 1;
|
||||
int maxZ = SectionPos.blockToSectionCoord(Mth.floor(box.maxZ)) + 1;
|
||||
return new WorldSectionBox(world, minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
public int numSections() {
|
||||
return (this.chunkX2 - this.chunkX1) * (this.chunkY2 - this.chunkY1) * (this.chunkZ2 - this.chunkZ1);
|
||||
}
|
||||
|
||||
public boolean matchesRelevantBlocksBox(AABB box) {
|
||||
return SectionPos.blockToSectionCoord(Mth.floor(box.minX) - 1) == this.chunkX1 &&
|
||||
SectionPos.blockToSectionCoord(Mth.floor(box.minY) - 1) == this.chunkY1 &&
|
||||
SectionPos.blockToSectionCoord(Mth.floor(box.minZ) - 1) == this.chunkZ1 &&
|
||||
SectionPos.blockToSectionCoord(Mth.ceil(box.maxX) + 1) + 1 == this.chunkX2 &&
|
||||
SectionPos.blockToSectionCoord(Mth.ceil(box.maxY) + 1) + 1 == this.chunkY2 &&
|
||||
SectionPos.blockToSectionCoord(Mth.ceil(box.maxZ) + 1) + 1 == this.chunkZ2;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.gensokyoreimagined.nitori.common.world;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import net.gensokyoreimagined.nitori.common.entity.block_tracking.ChunkSectionChangeCallback;
|
||||
import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
|
||||
import net.gensokyoreimagined.nitori.common.block.entity.movement_tracker.SectionedEntityMovementTracker;
|
||||
import net.gensokyoreimagined.nitori.common.util.deduplication.LithiumInterner;
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.entity.raid.Raid;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.gameevent.GameEventDispatcher;
|
||||
import net.minecraft.world.level.gameevent.GameEventListenerRegistry;
|
||||
|
||||
public interface LithiumData {
|
||||
|
||||
record Data(
|
||||
// Map of chunk position -> y section -> game event dispatcher
|
||||
// This should be faster than the chunk lookup, since there are usually a lot more chunks than
|
||||
// chunk with game event dispatchers (we only initialize them when non-empty set of listeners)
|
||||
// All Int2ObjectMap objects are also stored in a field of the corresponding WorldChunk.
|
||||
Long2ReferenceOpenHashMap<Int2ObjectMap<GameEventDispatcher>> gameEventDispatchersByChunk,
|
||||
|
||||
// Cached ominous banner, must not be mutated.
|
||||
ItemStack ominousBanner,
|
||||
|
||||
// Set of active mob navigations (active = have a path)
|
||||
ReferenceOpenHashSet<PathNavigation> activeNavigations,
|
||||
|
||||
// Block change tracker deduplication
|
||||
LithiumInterner<SectionedBlockChangeTracker> blockChangeTrackers,
|
||||
|
||||
// Entity movement tracker deduplication
|
||||
LithiumInterner<SectionedEntityMovementTracker<?, ?>> entityMovementTrackers,
|
||||
|
||||
// Block ChunkSection listeners
|
||||
Long2ReferenceOpenHashMap<ChunkSectionChangeCallback> chunkSectionChangeCallbacks
|
||||
) {
|
||||
public Data(Level world) {
|
||||
this(
|
||||
new Long2ReferenceOpenHashMap<>(),
|
||||
world.registryAccess().getOptionalWrapper(Registries.BANNER_PATTERN).map(Raid::getOminousBanner).orElse(null),
|
||||
new ReferenceOpenHashSet<>(),
|
||||
new LithiumInterner<>(),
|
||||
new LithiumInterner<>(),
|
||||
new Long2ReferenceOpenHashMap<>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LithiumData.Data lithium$getData();
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// Nitori Copyright (C) 2024 Gensokyo Reimagined
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Direction.class)
|
||||
public class MixinDirection {
|
||||
@Shadow @Final private static Direction[] VALUES;
|
||||
@Shadow @Final private int oppositeIndex;
|
||||
|
||||
/**
|
||||
* @author DoggySazHi
|
||||
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `from3DDataValue`
|
||||
*/
|
||||
@Overwrite
|
||||
public Direction getOpposite() {
|
||||
return VALUES[this.oppositeIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @author DoggySazHi
|
||||
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `Util.getRandom`
|
||||
*/
|
||||
@Overwrite
|
||||
public static Direction getRandom(RandomSource random) {
|
||||
return VALUES[random.nextInt(VALUES.length)];
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,46 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.alloc.composter;
|
||||
|
||||
//import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
|
||||
//import net.minecraft.world.WorldlyContainer;
|
||||
//import net.minecraft.core.Direction;
|
||||
//import org.spongepowered.asm.mixin.Mixin;
|
||||
//import org.spongepowered.asm.mixin.Overwrite;
|
||||
//
|
||||
//public class ComposterMixin {
|
||||
//
|
||||
// @Mixin(targets = "net.minecraft.block.ComposterBlock$ComposterInventory")
|
||||
// static abstract class ComposterBlockComposterInventoryMixin implements WorldlyContainer {
|
||||
// /**
|
||||
// * @author 2No2Name
|
||||
// * @reason avoid allocation
|
||||
// */
|
||||
// @Overwrite
|
||||
// public int[] getSlotsForFace(Direction side) {
|
||||
// return side == Direction.UP ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Mixin(targets = "net.minecraft.block.ComposterBlock$DummyInventory")
|
||||
// static abstract class ComposterBlockDummyInventoryMixin implements WorldlyContainer {
|
||||
// /**
|
||||
// * @author 2No2Name
|
||||
// * @reason avoid allocation
|
||||
// */
|
||||
// @Overwrite
|
||||
// public int[] getSlotsForFace(Direction side) {
|
||||
// return ArrayConstants.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Mixin(targets = "net.minecraft.block.ComposterBlock$FullComposterInventory")
|
||||
// static abstract class ComposterBlockFullComposterInventoryMixin implements WorldlyContainer {
|
||||
// /**
|
||||
// * @author 2No2Name
|
||||
// * @reason avoid allocation
|
||||
// */
|
||||
// @Overwrite
|
||||
// public int[] getSlotsForFace(Direction side) {
|
||||
// return side == Direction.DOWN ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
|
||||
import net.minecraft.world.WorldlyContainer;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
//TODO: Mixins are not getting dettected for some reason even if they are in the mixins.core???
|
||||
public class ComposterMixin {
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$InputContainer")
|
||||
static abstract class ComposterBlockComposterInventoryMixin implements WorldlyContainer {
|
||||
/**
|
||||
* @author 2No2Name
|
||||
* @reason avoid allocation
|
||||
*/
|
||||
@Overwrite
|
||||
public int[] getSlotsForFace(Direction side) {
|
||||
return side == Direction.UP ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$EmptyContainer")
|
||||
static abstract class ComposterBlockDummyInventoryMixin implements WorldlyContainer {
|
||||
/**
|
||||
* @author 2No2Name
|
||||
* @reason avoid allocation
|
||||
*/
|
||||
@Overwrite
|
||||
public int[] getSlotsForFace(Direction side) {
|
||||
return ArrayConstants.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.level.block.ComposterBlock$OutputContainer")
|
||||
static abstract class ComposterBlockFullComposterInventoryMixin implements WorldlyContainer {
|
||||
/**
|
||||
* @author 2No2Name
|
||||
* @reason avoid allocation
|
||||
*/
|
||||
@Overwrite
|
||||
public int[] getSlotsForFace(Direction side) {
|
||||
return side == Direction.DOWN ? ArrayConstants.ZERO : ArrayConstants.EMPTY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.block_entity_tickers;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import net.minecraft.world.ticks.LevelChunkTicks;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Coerce;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(LevelChunk.class)
|
||||
public class WorldChunkMixin {
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<BlockPos, ?> tickersInLevel;
|
||||
|
||||
@Inject(
|
||||
method = "<init>(Lnet/minecraft/world/level/Level;Lnet/minecraft/world/level/ChunkPos;Lnet/minecraft/world/level/chunk/UpgradeData;Lnet/minecraft/world/ticks/LevelChunkTicks;Lnet/minecraft/world/ticks/LevelChunkTicks;J[Lnet/minecraft/world/level/chunk/LevelChunkSection;Lnet/minecraft/world/level/chunk/LevelChunk$PostLoadProcessor;Lnet/minecraft/world/level/levelgen/blending/BlendingData;)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
@Coerce
|
||||
private void createFastUtilMap(Level world, ChunkPos pos, UpgradeData upgradeData, LevelChunkTicks<?> blockTickScheduler, LevelChunkTicks<?> fluidTickScheduler, long inhabitedTime, LevelChunkSection[] sectionArrayInitializer, LevelChunk.PostLoadProcessor entityLoader, BlendingData blendingData, CallbackInfo ci) {
|
||||
this.tickersInLevel = new Object2ObjectOpenHashMap<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.brain;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.minecraft.world.entity.ai.Brain;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
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.CallbackInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(Brain.class)
|
||||
public class BrainMixin {
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<?, ?> memories;
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<?, ?> sensors;
|
||||
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void reinitializeBrainCollections(Collection<?> memories, Collection<?> sensors, ImmutableList<?> memoryEntries, Supplier<?> codecSupplier, CallbackInfo ci) {
|
||||
this.memories = new Reference2ReferenceOpenHashMap<>(this.memories);
|
||||
this.sensors = new Reference2ReferenceLinkedOpenHashMap<>(this.sensors);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.entity_by_type;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.minecraft.util.ClassInstanceMultiMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Mutable;
|
||||
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.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(ClassInstanceMultiMap.class)
|
||||
public class TypeFilterableListMixin {
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Class<?>, List<?>> byClass;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void init(Class<?> elementType, CallbackInfo ci) {
|
||||
this.byClass = new Reference2ReferenceOpenHashMap<>(this.byClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.entity_filtering;
|
||||
|
||||
import net.minecraft.util.ClassInstanceMultiMap;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Patches {@link ClassInstanceMultiMap} to improve performance when entities are being queried in the world.
|
||||
*/
|
||||
@Mixin(ClassInstanceMultiMap.class)
|
||||
public class TypeFilterableListMixin<T> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<Class<?>, List<T>> byClass;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private List<T> allInstances;
|
||||
|
||||
/**
|
||||
* @reason Only perform the slow Class#isAssignableFrom(Class) if a list doesn't exist for the type, otherwise
|
||||
* we can assume it's already valid. The slow-path code is moved to a separate method to help the JVM inline this.
|
||||
* @author JellySquid
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Overwrite
|
||||
public <S> Collection<S> find(Class<S> type) {
|
||||
Collection<T> collection = this.byClass.get(type);
|
||||
|
||||
if (collection == null) {
|
||||
collection = this.createAllOfType(type);
|
||||
}
|
||||
|
||||
return (Collection<S>) Collections.unmodifiableCollection(collection);
|
||||
}
|
||||
|
||||
private <S> Collection<T> createAllOfType(Class<S> type) {
|
||||
List<T> list = new ArrayList<>();
|
||||
|
||||
for (T allElement : this.allInstances) {
|
||||
if (type.isInstance(allElement)) {
|
||||
list.add(allElement);
|
||||
}
|
||||
}
|
||||
|
||||
this.byClass.put(type, list);
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.entity.fast_retrieval;
|
||||
|
||||
import net.minecraft.util.AbortableIterationConsumer;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.entity.EntityAccess;
|
||||
import net.minecraft.world.level.entity.EntitySection;
|
||||
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
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.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(EntitySectionStorage.class)
|
||||
public abstract class SectionedEntityCacheMixin<T extends EntityAccess> {
|
||||
@Shadow
|
||||
@Nullable
|
||||
public abstract EntitySection<T> getSection(long sectionPos);
|
||||
|
||||
/**
|
||||
* @author 2No2Name
|
||||
* @reason avoid iterating through LongAVLTreeSet, possibly iterating over hundreds of irrelevant longs to save up to 8 hash set gets
|
||||
*/
|
||||
@Inject(
|
||||
method = "forEachAccessibleNonEmptySection",
|
||||
at = @At(
|
||||
value = "INVOKE_ASSIGN",
|
||||
shift = At.Shift.AFTER,
|
||||
target = "Lnet/minecraft/core/SectionPos;posToSectionCoord(D)I",
|
||||
ordinal = 5
|
||||
),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD,
|
||||
cancellable = true
|
||||
)
|
||||
public void forEachInBox(AABB box, AbortableIterationConsumer<EntitySection<T>> action, CallbackInfo ci, int i, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
if (maxX >= minX + 4 || maxZ >= minZ + 4) {
|
||||
return; // Vanilla is likely more optimized when shooting entities with TNT cannons over huge distances.
|
||||
// Choosing a cutoff of 4 chunk size, as it becomes more likely that these entity sections do not exist when
|
||||
// they are far away from the shot entity (player despawn range, position maybe not on the ground, etc)
|
||||
}
|
||||
ci.cancel();
|
||||
|
||||
// Vanilla order of the AVL long set is sorting by ascending long value. The x, y, z positions are packed into
|
||||
// a long with the x position's lowest 22 bits placed at the MSB.
|
||||
// Therefore the long is negative iff the 22th bit of the x position is set, which happens iff the x position
|
||||
// is negative. A positive x position will never have its 22th bit set, as these big coordinates are far outside
|
||||
// the world. y and z positions are treated as unsigned when sorting by ascending long value, as their sign bits
|
||||
// are placed somewhere inside the packed long
|
||||
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int z = Math.max(minZ, 0); z <= maxZ; z++) {
|
||||
if (this.forEachInColumn(x, minY, maxY, z, action).shouldAbort()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int bound = Math.min(-1, maxZ);
|
||||
for (int z = minZ; z <= bound; z++) {
|
||||
if (this.forEachInColumn(x, minY, maxY, z, action).shouldAbort()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private AbortableIterationConsumer.Continuation forEachInColumn(int x, int minY, int maxY, int z, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
AbortableIterationConsumer.Continuation ret = AbortableIterationConsumer.Continuation.CONTINUE;
|
||||
//y from negative to positive, but y is treated as unsigned
|
||||
for (int y = Math.max(minY, 0); y <= maxY; y++) {
|
||||
if ((ret = this.consumeSection(SectionPos.asLong(x, y, z), action)).shouldAbort()) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
int bound = Math.min(-1, maxY);
|
||||
for (int y = minY; y <= bound; y++) {
|
||||
if ((ret = this.consumeSection(SectionPos.asLong(x, y, z), action)).shouldAbort()) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private AbortableIterationConsumer.Continuation consumeSection(long pos, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
EntitySection<T> section = this.getSection(pos);
|
||||
//noinspection SizeReplaceableByIsEmpty
|
||||
if (section != null &&
|
||||
0 != section.size() /* util.entity_movement_tracking mixins modify isEmpty to include listener objects */
|
||||
&& section.getStatus().isAccessible()) {
|
||||
return action.accept(section);
|
||||
}
|
||||
return AbortableIterationConsumer.Continuation.CONTINUE;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,37 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.math.fast_util;
|
||||
|
||||
//import net.minecraft.core.Direction;
|
||||
//import net.minecraft.core.Direction.Axis;
|
||||
//import net.minecraft.util.RandomSource;
|
||||
//import org.spongepowered.asm.mixin.Final;
|
||||
//import org.spongepowered.asm.mixin.Mixin;
|
||||
//import org.spongepowered.asm.mixin.Overwrite;
|
||||
//import org.spongepowered.asm.mixin.Shadow;
|
||||
//
|
||||
//@Mixin(Direction.class)
|
||||
//public class DirectionMixin {
|
||||
// @Shadow
|
||||
// @Final
|
||||
// private static Direction[] ALL;
|
||||
//
|
||||
// @Shadow
|
||||
// @Final
|
||||
// private int idOpposite;
|
||||
//
|
||||
// /**
|
||||
// * @reason Avoid the modulo/abs operations
|
||||
// * @author JellySquid
|
||||
// */
|
||||
// @Overwrite
|
||||
// public Direction getOpposite() {
|
||||
// return ALL[this.idOpposite];
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @reason Do not allocate an excessive number of Direction arrays
|
||||
// * @author JellySquid
|
||||
// */
|
||||
// @Overwrite
|
||||
// public static Direction getRandom(RandomSource rand) {
|
||||
// return Direction.ALL[rand.nextInt(ALL.length)];
|
||||
// }
|
||||
//}
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Direction.class)
|
||||
public class DirectionMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private static Direction[] VALUES;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private int oppositeIndex;
|
||||
|
||||
/**
|
||||
* @reason Avoid the modulo/abs operations
|
||||
* @author JellySquid
|
||||
*/
|
||||
@Overwrite
|
||||
public Direction getOpposite() {
|
||||
return VALUES[this.oppositeIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @reason Do not allocate an excessive number of Direction arrays
|
||||
* @author JellySquid
|
||||
*/
|
||||
@Overwrite
|
||||
public static Direction getRandom(RandomSource rand) {
|
||||
return VALUES[rand.nextInt(VALUES.length)];
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
package net.gensokyoreimagined.nitori.mixin.math.sine_lut;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
@@ -0,0 +1,110 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.shapes.lazy_shape_context;
|
||||
|
||||
import net.minecraft.world.phys.shapes.EntityCollisionContext;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(EntityCollisionContext.class)
|
||||
public class EntityShapeContextMixin {
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private ItemStack heldItem;
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Predicate<FluidState> canStandOnFluid;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Nullable
|
||||
private Entity entity;
|
||||
|
||||
/**
|
||||
* Mixin the instanceof to always return false to avoid the expensive inventory access.
|
||||
* No need to use Opcodes.INSTANCEOF or similar.
|
||||
*/
|
||||
@SuppressWarnings("InvalidInjectorMethodSignature")
|
||||
@ModifyConstant(
|
||||
method = "<init>(Lnet/minecraft/world/entity/Entity;)V",
|
||||
constant = @Constant(classValue = LivingEntity.class, ordinal = 0)
|
||||
)
|
||||
private static boolean redirectInstanceOf(Object ignored, Class<?> constant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("InvalidInjectorMethodSignature")
|
||||
@ModifyConstant(
|
||||
method = "<init>(Lnet/minecraft/world/entity/Entity;)V",
|
||||
constant = @Constant(classValue = LivingEntity.class, ordinal = 2)
|
||||
)
|
||||
private static boolean redirectInstanceOf2(Object ignored, Class<?> constant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "<init>(Lnet/minecraft/world/entity/Entity;)V",
|
||||
at = @At("TAIL")
|
||||
)
|
||||
private void initFields(Entity entity, CallbackInfo ci) {
|
||||
this.heldItem = null;
|
||||
this.canStandOnFluid = null;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "isHoldingItem",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
public void isHolding(Item item, CallbackInfoReturnable<Boolean> cir) {
|
||||
this.nitori$initHeldItem();
|
||||
}
|
||||
|
||||
@Intrinsic
|
||||
public ItemStack getHeldItem() {
|
||||
return this.heldItem;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnresolvedMixinReference", "MixinAnnotationTarget"})
|
||||
@Inject(
|
||||
method = "getHeldItem",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
private void nitori$initHeldItem(CallbackInfoReturnable<ItemStack> callbackInfoReturnable) {
|
||||
this.nitori$initHeldItem();
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void nitori$initHeldItem() {
|
||||
if (this.heldItem == null) {
|
||||
this.heldItem = this.entity instanceof LivingEntity ? ((LivingEntity) this.entity).getMainHandItem() : ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "canStandOnFluid",
|
||||
at = @At("HEAD")
|
||||
)
|
||||
public void canWalkOnFluid(FluidState state, FluidState fluidState, CallbackInfoReturnable<Boolean> cir) {
|
||||
if (this.canStandOnFluid == null) {
|
||||
if (this.entity instanceof LivingEntity livingEntity) {
|
||||
this.canStandOnFluid = livingEntity::canStandOnFluid;
|
||||
} else {
|
||||
this.canStandOnFluid = (liquid) -> false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.util.block_tracking;
|
||||
|
||||
import me.jellysquid.mods.lithium.common.block.*;
|
||||
import net.gensokyoreimagined.nitori.common.entity.block_tracking.ChunkSectionChangeCallback;
|
||||
import net.gensokyoreimagined.nitori.common.entity.block_tracking.SectionedBlockChangeTracker;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
/**
|
||||
* Keep track of how many blocks that meet certain criteria are in this chunk section.
|
||||
* E.g. if no over-sized blocks are there, collision code can skip a few blocks.
|
||||
*
|
||||
* @author 2No2Name
|
||||
*/
|
||||
@Mixin(LevelChunkSection.class)
|
||||
public abstract class ChunkSectionMixin implements BlockCountingSection, BlockListeningSection {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private PalettedContainer<BlockState> blockStateContainer;
|
||||
|
||||
@Unique
|
||||
private short[] countsByFlag = null;
|
||||
@Unique
|
||||
private ChunkSectionChangeCallback changeListener;
|
||||
@Unique
|
||||
private short listeningMask;
|
||||
|
||||
@Unique
|
||||
private static void addToFlagCount(short[] countsByFlag, BlockState state, short change) {
|
||||
int flags = ((BlockStateFlagHolder) state).lithium$getAllFlags();
|
||||
int i;
|
||||
while ((i = Integer.numberOfTrailingZeros(flags)) < 32 && i < countsByFlag.length) {
|
||||
//either count up by one (prevFlag not set) or down by one (prevFlag set)
|
||||
countsByFlag[i] += change;
|
||||
flags &= ~(1 << i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean lithium$mayContainAny(TrackedBlockStatePredicate trackedBlockStatePredicate) {
|
||||
if (this.countsByFlag == null) {
|
||||
fastInitClientCounts();
|
||||
}
|
||||
return this.countsByFlag[trackedBlockStatePredicate.getIndex()] != (short) 0;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void fastInitClientCounts() {
|
||||
this.countsByFlag = new short[BlockStateFlags.NUM_TRACKED_FLAGS];
|
||||
for (TrackedBlockStatePredicate trackedBlockStatePredicate : BlockStateFlags.TRACKED_FLAGS) {
|
||||
if (this.blockStateContainer.hasAny(trackedBlockStatePredicate)) {
|
||||
//We haven't counted, so we just set the count so high that it never incorrectly reaches 0.
|
||||
//For most situations, this overestimation does not hurt client performance compared to correct counting,
|
||||
this.countsByFlag[trackedBlockStatePredicate.getIndex()] = 16 * 16 * 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "calculateCounts()V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/chunk/PalettedContainer;count(Lnet/minecraft/world/chunk/PalettedContainer$Counter;)V"
|
||||
)
|
||||
)
|
||||
private void initFlagCounters(PalettedContainer<BlockState> palettedContainer, PalettedContainer.Counter<BlockState> consumer) {
|
||||
palettedContainer.count((state, count) -> {
|
||||
consumer.accept(state, count);
|
||||
addToFlagCount(this.countsByFlag, state, (short) count);
|
||||
});
|
||||
}
|
||||
|
||||
@Inject(method = "calculateCounts()V", at = @At("HEAD"))
|
||||
private void createFlagCounters(CallbackInfo ci) {
|
||||
this.countsByFlag = new short[BlockStateFlags.NUM_TRACKED_FLAGS];
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "readDataPacket",
|
||||
at = @At(value = "HEAD")
|
||||
)
|
||||
private void resetData(FriendlyByteBuf buf, CallbackInfo ci) {
|
||||
this.countsByFlag = null;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "setBlockState(IIILnet/minecraft/block/BlockState;Z)Lnet/minecraft/block/BlockState;",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/block/BlockState;getFluidState()Lnet/minecraft/fluid/FluidState;",
|
||||
ordinal = 0,
|
||||
shift = At.Shift.BEFORE
|
||||
),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD
|
||||
)
|
||||
private void updateFlagCounters(int x, int y, int z, BlockState newState, boolean lock, CallbackInfoReturnable<BlockState> cir, BlockState oldState) {
|
||||
this.lithium$trackBlockStateChange(newState, oldState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$trackBlockStateChange(BlockState newState, BlockState oldState) {
|
||||
short[] countsByFlag = this.countsByFlag;
|
||||
if (countsByFlag == null) {
|
||||
return;
|
||||
}
|
||||
int prevFlags = ((BlockStateFlagHolder) oldState).lithium$getAllFlags();
|
||||
int flags = ((BlockStateFlagHolder) newState).lithium$getAllFlags();
|
||||
|
||||
int flagsXOR = prevFlags ^ flags;
|
||||
//we need to iterate over indices that changed or are in the listeningMask
|
||||
//Some Listening Flags are sensitive to both the previous and the new block. Others are only sensitive to
|
||||
//blocks that are different according to the predicate (XOR). For XOR, the block counting needs to be updated
|
||||
//as well.
|
||||
int iterateFlags = (~BlockStateFlags.LISTENING_MASK_OR & flagsXOR) |
|
||||
(BlockStateFlags.LISTENING_MASK_OR & this.listeningMask & (prevFlags | flags));
|
||||
int flagIndex;
|
||||
|
||||
while ((flagIndex = Integer.numberOfTrailingZeros(iterateFlags)) < 32 && flagIndex < countsByFlag.length) {
|
||||
int flagBit = 1 << flagIndex;
|
||||
//either count up by one (prevFlag not set) or down by one (prevFlag set)
|
||||
if ((flagsXOR & flagBit) != 0) {
|
||||
countsByFlag[flagIndex] += (short) (1 - (((prevFlags >>> flagIndex) & 1) << 1));
|
||||
}
|
||||
if ((this.listeningMask & flagBit) != 0) {
|
||||
this.listeningMask = this.changeListener.onBlockChange(flagIndex, this);
|
||||
}
|
||||
iterateFlags &= ~flagBit;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$addToCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker, long sectionPos, Level world) {
|
||||
if (this.changeListener == null) {
|
||||
if (sectionPos == Long.MIN_VALUE || world == null) {
|
||||
throw new IllegalArgumentException("Expected world and section pos during intialization!");
|
||||
}
|
||||
this.changeListener = ChunkSectionChangeCallback.create(sectionPos, world);
|
||||
}
|
||||
|
||||
this.listeningMask = this.changeListener.addTracker(tracker, blockGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$removeFromCallback(ListeningBlockStatePredicate blockGroup, SectionedBlockChangeTracker tracker) {
|
||||
if (this.changeListener != null) {
|
||||
this.listeningMask = this.changeListener.removeTracker(tracker, blockGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Unique
|
||||
public void lithium$invalidateListeningSection(ChunkSectionPos sectionPos) {
|
||||
if (this.listeningMask != 0) {
|
||||
this.changeListener.onChunkSectionInvalidated(sectionPos);
|
||||
this.listeningMask = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping;
|
||||
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
|
||||
@Mixin(targets = "net/minecraft/world/level/chunk/LevelChunk$RebindableTickingBlockEntityWrapper")
|
||||
public interface WrappedBlockEntityTickInvokerAccessor {
|
||||
@Invoker
|
||||
void callRebind(TickingBlockEntity wrapped);
|
||||
|
||||
@Accessor
|
||||
TickingBlockEntity getTicker();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.campfire;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.entity.SleepingBlockEntity;
|
||||
import net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.CampfireBlockEntity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(CampfireBlockEntity.class)
|
||||
public class CampfireBlockEntityMixin extends BlockEntity implements SleepingBlockEntity {
|
||||
|
||||
private WrappedBlockEntityTickInvokerAccessor tickWrapper = null;
|
||||
private TickingBlockEntity sleepingTicker = null;
|
||||
|
||||
public CampfireBlockEntityMixin(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedBlockEntityTickInvokerAccessor lithium$getTickWrapper() {
|
||||
return tickWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$setTickWrapper(WrappedBlockEntityTickInvokerAccessor tickWrapper) {
|
||||
this.tickWrapper = tickWrapper;
|
||||
this.lithium$setSleepingTicker(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TickingBlockEntity lithium$getSleepingTicker() {
|
||||
return sleepingTicker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) {
|
||||
this.sleepingTicker = sleepingTicker;
|
||||
}
|
||||
|
||||
|
||||
@Inject(
|
||||
method = "placeFood",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/core/NonNullList;set(ILjava/lang/Object;)Ljava/lang/Object;")
|
||||
)
|
||||
private void wakeUpOnAddItem(Entity user, ItemStack stack, int cookTime, CallbackInfoReturnable<Boolean> cir) {
|
||||
this.wakeUpNow();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "loadAdditional",
|
||||
at = @At(value = "RETURN")
|
||||
)
|
||||
private void wakeUpOnReadNbt(CompoundTag nbt, HolderLookup.Provider registryLookup, CallbackInfo ci) {
|
||||
this.wakeUpNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.campfire.lit;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.entity.SleepingBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.CampfireBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.event.block.BlockCookEvent;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Mixin(CampfireBlockEntity.class)
|
||||
public abstract class CampfireBlockEntityMixin extends BlockEntity implements SleepingBlockEntity {
|
||||
|
||||
public CampfireBlockEntityMixin(BlockPos pos, BlockState state) {
|
||||
super(BlockEntityType.CAMPFIRE, pos, state);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "cookTick",
|
||||
at = @At("RETURN"),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD
|
||||
)
|
||||
private static void trySleepLit(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire, CallbackInfo ci, boolean flag) {
|
||||
if (!flag) {
|
||||
CampfireBlockEntityMixin self = (CampfireBlockEntityMixin) (Object) campfire;
|
||||
self.nitori$startSleeping();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.campfire.unlit;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.entity.SleepingBlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.entity.CampfireBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
@Mixin(CampfireBlockEntity.class)
|
||||
public abstract class CampfireBlockEntityMixin extends BlockEntity implements SleepingBlockEntity {
|
||||
|
||||
public CampfireBlockEntityMixin(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "cooldownTick",
|
||||
at = @At("RETURN"),
|
||||
locals = LocalCapture.CAPTURE_FAILHARD
|
||||
)
|
||||
private static void trySleepLit(Level world, BlockPos pos, BlockState state, CampfireBlockEntity campfire, CallbackInfo ci, boolean flag) {
|
||||
if (!flag) {
|
||||
CampfireBlockEntityMixin self = (CampfireBlockEntityMixin) (Object) campfire;
|
||||
self.nitori$startSleeping();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.furnace;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.block.entity.SleepingBlockEntity;
|
||||
import net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.entity.TickingBlockEntity;
|
||||
import org.spongepowered.asm.mixin.Intrinsic;
|
||||
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.CallbackInfo;
|
||||
|
||||
@Mixin(AbstractFurnaceBlockEntity.class)
|
||||
public abstract class AbstractFurnaceBlockEntityMixin extends BlockEntity implements SleepingBlockEntity {
|
||||
|
||||
@Shadow
|
||||
protected abstract boolean isLit();
|
||||
|
||||
@Shadow
|
||||
public int cookingProgress;
|
||||
private WrappedBlockEntityTickInvokerAccessor tickWrapper = null;
|
||||
private TickingBlockEntity sleepingTicker = null;
|
||||
|
||||
public AbstractFurnaceBlockEntityMixin(BlockEntityType<?> type, BlockPos pos, BlockState state) {
|
||||
super(type, pos, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedBlockEntityTickInvokerAccessor lithium$getTickWrapper() {
|
||||
return tickWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$setTickWrapper(WrappedBlockEntityTickInvokerAccessor tickWrapper) {
|
||||
this.tickWrapper = tickWrapper;
|
||||
this.lithium$setSleepingTicker(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TickingBlockEntity lithium$getSleepingTicker() {
|
||||
return sleepingTicker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lithium$setSleepingTicker(TickingBlockEntity sleepingTicker) {
|
||||
this.sleepingTicker = sleepingTicker;
|
||||
}
|
||||
|
||||
@Inject(method = "serverTick", at = @At("RETURN"))
|
||||
private static void checkSleep(Level world, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity blockEntity, CallbackInfo ci) {
|
||||
((AbstractFurnaceBlockEntityMixin) (Object) blockEntity).checkSleep(state);
|
||||
}
|
||||
|
||||
private void checkSleep(BlockState state) {
|
||||
if (!this.isLit() && this.cookingProgress == 0 && (state.is(Blocks.FURNACE) || state.is(Blocks.BLAST_FURNACE) || state.is(Blocks.SMOKER)) && this.level != null) {
|
||||
this.nitori$startSleeping();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "loadAdditional", at = @At("RETURN" ))
|
||||
private void wakeUpAfterFromTag(CallbackInfo ci) {
|
||||
if (this.isSleeping() && this.level != null && !this.level.isClientSide) {
|
||||
this.wakeUpNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Intrinsic
|
||||
public void setChanged() {
|
||||
super.setChanged();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MixinAnnotationTarget", "UnresolvedMixinReference"})
|
||||
@Inject(method = "setChanged()V", at = @At("RETURN"))
|
||||
private void wakeOnMarkDirty(CallbackInfo ci) {
|
||||
if (this.isSleeping() && this.level != null && !this.level.isClientSide) {
|
||||
this.wakeUpNow();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "Nitori",
|
||||
"version": "1.2-SNAPSHOT",
|
||||
"version": "1.3-SNAPSHOT",
|
||||
"mixins": [
|
||||
"mixins.core.json"
|
||||
]
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"MixinBlockPos",
|
||||
"MixinChunkEntitySlices",
|
||||
"MixinCraftPlayer",
|
||||
"MixinDirection",
|
||||
"MixinEntity",
|
||||
"MixinEntitySectionStorage",
|
||||
"MixinGameRules",
|
||||
@@ -21,7 +20,7 @@
|
||||
"MixinLevel",
|
||||
"MixinLevelStorageAccess",
|
||||
"MixinMob",
|
||||
"MixinMth",
|
||||
"math.sine_lut.MixinMth",
|
||||
"MixinNoiseBasedChunkGenerator",
|
||||
"MixinPlayer",
|
||||
"MixinPlayerList",
|
||||
@@ -32,11 +31,26 @@
|
||||
"MixinWorldGenRegion",
|
||||
"alloc.chunk_ticking.ServerChunkManagerMixin",
|
||||
"alloc.blockstate.StateMixin",
|
||||
"alloc.composter.ComposterMixin$ComposterBlockFullComposterInventoryMixin",
|
||||
"alloc.composter.ComposterMixin$ComposterBlockDummyInventoryMixin",
|
||||
"alloc.composter.ComposterMixin$ComposterBlockComposterInventoryMixin",
|
||||
"util.MixinLevelBlockEntityRetrieval",
|
||||
"cached_hashcode.BlockNeighborGroupMixin",
|
||||
"shapes.blockstate_cache.BlockMixin",
|
||||
"shapes.lazy_shape_context.EntityShapeContextMixin",
|
||||
"entity.fast_retrieval.SectionedEntityCacheMixin",
|
||||
"math.fast_blockops.DirectionMixin",
|
||||
"math.fast_blockops.BlockPosMixin",
|
||||
"ai.sensor.secondary_poi.SecondaryPointsOfInterestSensorMixin",
|
||||
"math.fast_util.AxisCycleDirectionMixin$ForwardMixin",
|
||||
"math.fast_util.AxisCycleDirectionMixin$BackwardMixin",
|
||||
"math.fast_util.DirectionMixin",
|
||||
"collections.entity_filtering.TypeFilterableListMixin",
|
||||
"collections.entity_by_type.TypeFilterableListMixin",
|
||||
"collections.block_entity_tickers.WorldChunkMixin",
|
||||
"world.block_entity_ticking.sleeping.WrappedBlockEntityTickInvokerAccessor",
|
||||
"world.block_entity_ticking.sleeping.campfire.CampfireBlockEntityMixin",
|
||||
"world.block_entity_ticking.sleeping.campfire.unlit.CampfireBlockEntityMixin",
|
||||
"world.block_entity_ticking.sleeping.campfire.lit.CampfireBlockEntityMixin",
|
||||
"world.block_entity_ticking.sleeping.furnace.AbstractFurnaceBlockEntityMixin"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user