This commit is contained in:
Taiyou06
2024-07-07 10:35:20 +03:00
parent feaec74c94
commit 9fb9753202
32 changed files with 1734 additions and 133 deletions

View File

@@ -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

View File

@@ -0,0 +1,4 @@
package net.gensokyoreimagined.nitori.common.ai.pathing;
public class BlockStatePathingCache {
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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]);
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,4 @@
package net.gensokyoreimagined.nitori.common.block.entity.movement_tracker;
public class SectionedEntityMovementTracker {
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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)];
}
}

View File

@@ -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;
}
}
}

View File

@@ -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<>();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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)];
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,6 +1,6 @@
{
"id": "Nitori",
"version": "1.2-SNAPSHOT",
"version": "1.3-SNAPSHOT",
"mixins": [
"mixins.core.json"
]

View File

@@ -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"
]
}