@@ -1,5 +1,5 @@
|
|||||||
group=net.gensokyoreimagined.nitori
|
group=net.gensokyoreimagined.nitori
|
||||||
version=1.2-SNAPSHOT
|
version=1.3-SNAPSHOT
|
||||||
description=Converting patches into mixins, for the Ignite Framework
|
description=Converting patches into mixins, for the Ignite Framework
|
||||||
|
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.access;
|
||||||
|
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.server.level.ChunkHolder;
|
||||||
|
import net.minecraft.server.level.PlayerMap;
|
||||||
|
import net.minecraft.server.level.ServerChunkCache;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.util.thread.BlockableEventLoop;
|
||||||
|
import org.apache.commons.lang3.mutable.MutableObject;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(ServerChunkCache.class)
|
||||||
|
public interface IThreadedAnvilChunkStorage {
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
ChunkHolder invokeGetCurrentChunkHolder(long pos);
|
||||||
|
|
||||||
|
@Invoker
|
||||||
|
ChunkHolder invokeGetChunkHolder(long pos);
|
||||||
|
|
||||||
|
@Accessor
|
||||||
|
ServerLevel getLevel();
|
||||||
|
|
||||||
|
@Accessor
|
||||||
|
PlayerMap getPlayerChunkWatchingManager();
|
||||||
|
|
||||||
|
@Accessor
|
||||||
|
BlockableEventLoop<Runnable> getMainThreadExecutor();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.access;
|
||||||
|
|
||||||
|
public interface ITypeFilterableList {
|
||||||
|
|
||||||
|
Object[] getBackingArray();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.api.inventory;
|
||||||
|
|
||||||
|
import net.minecraft.core.NonNullList;
|
||||||
|
import net.minecraft.world.entity.vehicle.ContainerEntity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the ability for mods to allow Lithium's hopper optimizations to access their inventories' for item transfers.
|
||||||
|
* This exists because Lithium's optimized hopper logic will only interact with inventories more efficiently than
|
||||||
|
* vanilla if the stack list can be directly accessed and replaced with Lithium's custom stack list.
|
||||||
|
* It is not required to implement this interface, but doing so will allow the mod's inventories to benefit from
|
||||||
|
* Lithium's optimizations.
|
||||||
|
* <p>
|
||||||
|
* This interface should be implemented by your {@link net.minecraft.inventory.Inventory} or
|
||||||
|
* {@link net.minecraft.inventory.SidedInventory} type to access the stack list.
|
||||||
|
* <p>
|
||||||
|
* An inventory must not extend {@link net.minecraft.block.entity.BlockEntity} if it has a supporting block that
|
||||||
|
* implements {@link net.minecraft.block.InventoryProvider}.
|
||||||
|
* <p>
|
||||||
|
* The hopper interaction behavior of a LithiumInventory should only change if the content of the inventory
|
||||||
|
* stack list also changes. For example, an inventory which only accepts an item if it already contains an item of the
|
||||||
|
* same type would work fine (changing the acceptance condition only happens when changing the inventory contents here).
|
||||||
|
* However, an inventory which accepts an item only if a certain block is near its position will need to signal this
|
||||||
|
* change to hoppers by calling {@link LithiumNonNullList#changedInteractionConditions()}.
|
||||||
|
*
|
||||||
|
* @author 2No2Name
|
||||||
|
*/
|
||||||
|
public interface LithiumInventory extends ContainerEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the inventory stack list of this inventory.
|
||||||
|
*
|
||||||
|
* @return inventory stack list
|
||||||
|
*/
|
||||||
|
NonNullList<ItemStack> getInventoryLithium();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the inventory stack list of this inventory.
|
||||||
|
* Used to replace the stack list with Lithium's custom stack list.
|
||||||
|
*
|
||||||
|
* @param inventory inventory stack list
|
||||||
|
*/
|
||||||
|
void setInventoryLithium(NonNullList<ItemStack> inventory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the loot like a hopper access would do in vanilla.
|
||||||
|
* <p>
|
||||||
|
* If a modded inventory has custom loot generation code, it will be required to override this
|
||||||
|
* loot generation method. Otherwise, its loot may be generated too late.
|
||||||
|
*/
|
||||||
|
default void generateLootLithium() {
|
||||||
|
if (this instanceof RandomizableContainerBlockEntity) {
|
||||||
|
((RandomizableContainerBlockEntity) this).unpackLootTable(null);
|
||||||
|
}
|
||||||
|
if (this instanceof ContainerEntity) {
|
||||||
|
((ContainerEntity) this).unpackChestVehicleLootTable(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.ai.pathing;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.pathfinder.PathType;
|
||||||
|
|
||||||
|
public interface BlockStatePathingCache {
|
||||||
|
PathType lithium$getPathNodeType();
|
||||||
|
PathType lithium$getNeighborPathNodeType();
|
||||||
|
}
|
||||||
@@ -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 net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.pathfinder.PathfindingContext;
|
||||||
|
import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
|
||||||
|
import net.minecraft.world.level.pathfinder.BinaryHeap;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.pathfinder.PathType;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.ai.pathing.PathContextAccessor;
|
||||||
|
|
||||||
|
public abstract class PathNodeCache {
|
||||||
|
private static boolean isChunkSectionDangerousNeighbor(LevelChunkSection section) {
|
||||||
|
return section.getStates().maybeHas(state -> getNeighborPathType(state) != PathType.OPEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PathType getPathType(BlockState state) {
|
||||||
|
return ((BlockStatePathingCache) state).lithium$getPathNodeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PathType getNeighborPathType(BlockBehaviour.BlockStateBase 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(LevelChunkSection section) {
|
||||||
|
// Empty sections can never contribute a danger
|
||||||
|
if (section.hasOnlyAir()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BlockStateFlags.ENABLED) {
|
||||||
|
return !((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.PATH_NOT_OPEN);
|
||||||
|
}
|
||||||
|
return !isChunkSectionDangerousNeighbor(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PathType getNodeTypeFromNeighbors(PathfindingContext context, int x, int y, int z, PathType fallback) {
|
||||||
|
BlockGetter world = context.level();
|
||||||
|
|
||||||
|
LevelChunkSection 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.isOutsideBuildHeight(y)) {
|
||||||
|
ChunkAccess 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.getSections()[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.MutableBlockPos pos = ((PathContextAccessor) context).getMutablePos().set(adjX, adjY, adjZ);
|
||||||
|
state = world.getBlockState(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.isAir()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PathType neighborType = PathNodeCache.getNeighborPathType(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.checkNeighbourBlocks(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 = PathType.OPEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (neighborType != PathType.OPEN) {
|
||||||
|
return neighborType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.block;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
public interface BlockCountingSection {
|
||||||
|
boolean lithium$mayContainAny(TrackedBlockStatePredicate trackedBlockStatePredicate);
|
||||||
|
|
||||||
|
void lithium$trackBlockStateChange(BlockState newState, BlockState oldState);
|
||||||
|
}
|
||||||
@@ -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,28 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.block;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
import net.minecraft.world.level.material.FluidState;
|
||||||
|
|
||||||
|
public class BlockStateCounter implements PalettedContainer.CountConsumer<BlockState> {
|
||||||
|
public int nonEmptyBlockCount;
|
||||||
|
public int randomTickableBlockCount;
|
||||||
|
public int nonEmptyFluidCount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(BlockState arg, int i) {
|
||||||
|
FluidState lv = arg.getFluidState();
|
||||||
|
if (!arg.isAir()) {
|
||||||
|
this.nonEmptyBlockCount += i;
|
||||||
|
if (arg.isRandomlyTicking()) {
|
||||||
|
this.randomTickableBlockCount += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!lv.isEmpty()) {
|
||||||
|
this.nonEmptyBlockCount += i;
|
||||||
|
if (lv.isRandomlyTicking()) {
|
||||||
|
this.nonEmptyFluidCount += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.block;
|
||||||
|
|
||||||
|
public interface BlockStateFlagHolder {
|
||||||
|
int lithium$getAllFlags();
|
||||||
|
}
|
||||||
@@ -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.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.tags.FluidTags;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.pathfinder.PathType;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class BlockStateFlags {
|
||||||
|
public static final boolean ENABLED = BlockCountingSection.class.isAssignableFrom(LevelChunkSection.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.shapeExceedsCube();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
countingFlags.add(OVERSIZED_SHAPE);
|
||||||
|
|
||||||
|
if (FluidCachingEntity.class.isAssignableFrom(Entity.class)) {
|
||||||
|
WATER = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||||
|
@Override
|
||||||
|
public boolean test(BlockState operand) {
|
||||||
|
return operand.getFluidState().getType().is(FluidTags.WATER);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
countingFlags.add(WATER);
|
||||||
|
|
||||||
|
LAVA = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||||
|
@Override
|
||||||
|
public boolean test(BlockState operand) {
|
||||||
|
return operand.getFluidState().getType().is(FluidTags.LAVA);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
countingFlags.add(LAVA);
|
||||||
|
} else {
|
||||||
|
WATER = null;
|
||||||
|
LAVA = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BlockStatePathingCache.class.isAssignableFrom(BlockBehaviour.BlockStateBase.class)) {
|
||||||
|
PATH_NOT_OPEN = new TrackedBlockStatePredicate(countingFlags.size()) {
|
||||||
|
@Override
|
||||||
|
public boolean test(BlockState operand) {
|
||||||
|
return PathNodeCache.getNeighborPathType(operand) != PathType.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,30 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.block;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public abstract class TrackedBlockStatePredicate implements Predicate<BlockState> {
|
||||||
|
public static final AtomicBoolean FULLY_INITIALIZED;
|
||||||
|
|
||||||
|
static {
|
||||||
|
FULLY_INITIALIZED = new AtomicBoolean(false);
|
||||||
|
if (!BlockStateFlags.ENABLED) { //classload the BlockStateFlags class which initializes the content of ALL_FLAGS
|
||||||
|
System.out.println("Lithium Cached BlockState Flags are disabled!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
public TrackedBlockStatePredicate(int index) {
|
||||||
|
if (FULLY_INITIALIZED.get()) {
|
||||||
|
throw new IllegalStateException("Lithium Cached BlockState Flags: Cannot register more flags after assuming to be fully initialized.");
|
||||||
|
}
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return this.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,9 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.chunkwatching;
|
||||||
|
|
||||||
|
public interface PlayerClientVDTracking {
|
||||||
|
|
||||||
|
boolean isClientViewDistanceChanged();
|
||||||
|
|
||||||
|
int getClientViewDistance();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ByteOpenHashMap;
|
||||||
|
import net.gensokyoreimagined.nitori.common.reflection.ReflectionUtil;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
|
||||||
|
import net.minecraft.world.entity.monster.Shulker;
|
||||||
|
import net.minecraft.world.entity.projectile.windcharge.WindCharge;
|
||||||
|
import net.minecraft.world.entity.vehicle.Minecart;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for grouping Entity classes by some property for use in TypeFilterableList
|
||||||
|
* It is intended that an EntityClassGroup acts as if it was immutable, however we cannot predict which subclasses of
|
||||||
|
* Entity might appear. Therefore we evaluate whether a class belongs to the class group when it is first seen.
|
||||||
|
* Once a class was evaluated the result of it is cached and cannot be changed.
|
||||||
|
*
|
||||||
|
* @author 2No2Name
|
||||||
|
*/
|
||||||
|
public class EntityClassGroup {
|
||||||
|
public static final EntityClassGroup CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE; //aka entities that will attempt to collide with all other entities when moving
|
||||||
|
|
||||||
|
static {
|
||||||
|
// String remapped_collidesWith = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_1297", "method_30949", "(Lnet/minecraft/class_1297;)Z");
|
||||||
|
String remapped_collidesWith = "canCollideWith";
|
||||||
|
CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE = new EntityClassGroup(
|
||||||
|
(Class<?> entityClass) -> ReflectionUtil.hasMethodOverride(entityClass, Entity.class, true, remapped_collidesWith, Entity.class));
|
||||||
|
|
||||||
|
//sanity check: in case intermediary mappings changed, we fail
|
||||||
|
if ((!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(Minecart.class))) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
if ((!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(WindCharge.class)) || (!CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(WindCharge.class))) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
if ((CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(Shulker.class))) {
|
||||||
|
//should not throw an Error here, because another mod *could* add the method to ShulkerEntity. Warning when this sanity check fails.
|
||||||
|
Logger.getLogger("Lithium EntityClassGroup").warning("Either Lithium EntityClassGroup is broken or something else gave Shulkers the minecart-like collision behavior.");
|
||||||
|
}
|
||||||
|
CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Predicate<Class<?>> classFitEvaluator;
|
||||||
|
private volatile Reference2ByteOpenHashMap<Class<?>> class2GroupContains;
|
||||||
|
|
||||||
|
public EntityClassGroup(Predicate<Class<?>> classFitEvaluator) {
|
||||||
|
this.class2GroupContains = new Reference2ByteOpenHashMap<>();
|
||||||
|
Objects.requireNonNull(classFitEvaluator);
|
||||||
|
this.classFitEvaluator = classFitEvaluator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.class2GroupContains = new Reference2ByteOpenHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(Class<?> entityClass) {
|
||||||
|
byte contains = this.class2GroupContains.getOrDefault(entityClass, (byte) 2);
|
||||||
|
if (contains != 2) {
|
||||||
|
return contains == 1;
|
||||||
|
} else {
|
||||||
|
return this.testAndAddClass(entityClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean testAndAddClass(Class<?> entityClass) {
|
||||||
|
byte contains;
|
||||||
|
//synchronizing here to avoid multiple threads replacing the map at the same time, and therefore possibly undoing progress
|
||||||
|
//it could also be fixed by using an AtomicReference's CAS, but we are writing very rarely (less than 150 times for the total game runtime in vanilla)
|
||||||
|
synchronized (this) {
|
||||||
|
//test the same condition again after synchronizing, as the collection might have been updated while this thread blocked
|
||||||
|
contains = this.class2GroupContains.getOrDefault(entityClass, (byte) 2);
|
||||||
|
if (contains != 2) {
|
||||||
|
return contains == 1;
|
||||||
|
}
|
||||||
|
//construct new map instead of updating the old map to avoid thread safety problems
|
||||||
|
//the map is not modified after publication
|
||||||
|
Reference2ByteOpenHashMap<Class<?>> newMap = this.class2GroupContains.clone();
|
||||||
|
contains = this.classFitEvaluator.test(entityClass) ? (byte) 1 : (byte) 0;
|
||||||
|
newMap.put(entityClass, contains);
|
||||||
|
//publish the new map in a volatile field, so that all threads reading after this write can also see all changes to the map done before the write
|
||||||
|
this.class2GroupContains = newMap;
|
||||||
|
}
|
||||||
|
return contains == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NoDragonClassGroup extends EntityClassGroup {
|
||||||
|
public static final NoDragonClassGroup BOAT_SHULKER_LIKE_COLLISION; //aka entities that other entities will do block-like collisions with when moving
|
||||||
|
|
||||||
|
static {
|
||||||
|
// String remapped_isCollidable = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_1297", "method_30948", "()Z");
|
||||||
|
String remapped_isCollidable = "canBeCollidedWith";
|
||||||
|
BOAT_SHULKER_LIKE_COLLISION = new NoDragonClassGroup(
|
||||||
|
(Class<?> entityClass) -> ReflectionUtil.hasMethodOverride(entityClass, Entity.class, true, remapped_isCollidable));
|
||||||
|
|
||||||
|
if ((!BOAT_SHULKER_LIKE_COLLISION.contains(Shulker.class))) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
BOAT_SHULKER_LIKE_COLLISION.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NoDragonClassGroup(Predicate<Class<?>> classFitEvaluator) {
|
||||||
|
super(classFitEvaluator);
|
||||||
|
if (classFitEvaluator.test(EnderDragon.class)) {
|
||||||
|
throw new IllegalArgumentException("EntityClassGroup.NoDragonClassGroup cannot be initialized: Must exclude EnderDragonEntity!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity;
|
||||||
|
|
||||||
|
public interface FluidCachingEntity {
|
||||||
|
}
|
||||||
@@ -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.of(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,18 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||||
|
|
||||||
|
public interface EntityMovementTrackerSection {
|
||||||
|
void lithium$addListener(SectionedEntityMovementTracker<?, ?> listener);
|
||||||
|
|
||||||
|
void lithium$removeListener(EntitySectionStorage<?> sectionedEntityCache, SectionedEntityMovementTracker<?, ?> listener);
|
||||||
|
|
||||||
|
void lithium$trackEntityMovement(int notificationMask, long time);
|
||||||
|
|
||||||
|
long lithium$getChangeTime(int trackedClass);
|
||||||
|
|
||||||
|
<S, E extends EntityAccess> void lithium$listenToMovementOnce(SectionedEntityMovementTracker<E, S> listener, int trackedClass);
|
||||||
|
|
||||||
|
<S, E extends EntityAccess> void lithium$removeListenToMovementOnce(SectionedEntityMovementTracker<E, S> listener, int trackedClass);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||||
|
import net.gensokyoreimagined.nitori.api.inventory.LithiumInventory;
|
||||||
|
import net.minecraft.world.entity.item.ItemEntity;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.level.block.entity.HopperBlockEntity;
|
||||||
|
import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class MovementTrackerHelper {
|
||||||
|
public static final List<Class<?>> MOVEMENT_NOTIFYING_ENTITY_CLASSES;
|
||||||
|
public static volatile Reference2IntOpenHashMap<Class<? extends EntityAccess>> CLASS_2_NOTIFY_MASK;
|
||||||
|
public static final int NUM_MOVEMENT_NOTIFYING_CLASSES;
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (LithiumInventory.class.isAssignableFrom(HopperBlockEntity.class)) {
|
||||||
|
MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of(ItemEntity.class, Inventory.class);
|
||||||
|
} else {
|
||||||
|
MOVEMENT_NOTIFYING_ENTITY_CLASSES = List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
CLASS_2_NOTIFY_MASK = new Reference2IntOpenHashMap<>();
|
||||||
|
CLASS_2_NOTIFY_MASK.defaultReturnValue(-1);
|
||||||
|
NUM_MOVEMENT_NOTIFYING_CLASSES = MOVEMENT_NOTIFYING_ENTITY_CLASSES.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getNotificationMask(Class<? extends EntityAccess> entityClass) {
|
||||||
|
int notificationMask = CLASS_2_NOTIFY_MASK.getInt(entityClass);
|
||||||
|
if (notificationMask == -1) {
|
||||||
|
notificationMask = calculateNotificationMask(entityClass);
|
||||||
|
}
|
||||||
|
return notificationMask;
|
||||||
|
}
|
||||||
|
private static int calculateNotificationMask(Class<? extends EntityAccess> entityClass) {
|
||||||
|
int mask = 0;
|
||||||
|
for (int i = 0; i < MOVEMENT_NOTIFYING_ENTITY_CLASSES.size(); i++) {
|
||||||
|
Class<?> superclass = MOVEMENT_NOTIFYING_ENTITY_CLASSES.get(i);
|
||||||
|
if (superclass.isAssignableFrom(entityClass)) {
|
||||||
|
mask |= 1 << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//progress can be lost here, but it can only cost performance
|
||||||
|
//copy on write followed by publication in volatile field guarantees visibility of the final state
|
||||||
|
Reference2IntOpenHashMap<Class<? extends EntityAccess>> copy = CLASS_2_NOTIFY_MASK.clone();
|
||||||
|
copy.put(entityClass, mask);
|
||||||
|
CLASS_2_NOTIFY_MASK = copy;
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
|
||||||
|
|
||||||
|
public interface SectionedEntityMovementListener {
|
||||||
|
void lithium$handleEntityMovement(Class<?> category);
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.movement_tracker;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.HashCommon;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||||
|
import net.gensokyoreimagined.nitori.common.util.tuples.WorldSectionBox;
|
||||||
|
import net.gensokyoreimagined.nitori.common.world.LithiumData;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerEntityManagerAccessor;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerWorldAccessor;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
import net.minecraft.world.level.entity.EntitySection;
|
||||||
|
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public abstract class SectionedEntityMovementTracker<E extends EntityAccess, S> {
|
||||||
|
final WorldSectionBox trackedWorldSections;
|
||||||
|
final Class<S> clazz;
|
||||||
|
private final int trackedClass;
|
||||||
|
ArrayList<EntitySection<E>> sortedSections;
|
||||||
|
boolean[] sectionVisible;
|
||||||
|
private int timesRegistered;
|
||||||
|
private final ArrayList<EntityMovementTrackerSection> sectionsNotListeningTo;
|
||||||
|
|
||||||
|
private long maxChangeTime;
|
||||||
|
|
||||||
|
private ReferenceOpenHashSet<SectionedEntityMovementListener> sectionedEntityMovementListeners;
|
||||||
|
|
||||||
|
public SectionedEntityMovementTracker(WorldSectionBox interactionChunks, Class<S> clazz) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
this.trackedWorldSections = interactionChunks;
|
||||||
|
this.trackedClass = MovementTrackerHelper.MOVEMENT_NOTIFYING_ENTITY_CLASSES.indexOf(clazz);
|
||||||
|
assert this.trackedClass != -1;
|
||||||
|
this.sectionedEntityMovementListeners = null;
|
||||||
|
this.sectionsNotListeningTo = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return HashCommon.mix(this.trackedWorldSections.hashCode()) ^ HashCommon.mix(this.trackedClass) ^ this.getClass().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj.getClass() == this.getClass() &&
|
||||||
|
this.clazz == ((SectionedEntityMovementTracker<?, ?>) obj).clazz &&
|
||||||
|
this.trackedWorldSections.equals(((SectionedEntityMovementTracker<?, ?>) obj).trackedWorldSections);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to quickly check whether any relevant entities moved inside the relevant entity sections after
|
||||||
|
* the last interaction attempt.
|
||||||
|
*
|
||||||
|
* @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.sectionsNotListeningTo.isEmpty()) {
|
||||||
|
this.setChanged(this.listenToAllSectionsAndGetMaxChangeTime());
|
||||||
|
return lastCheckedTime > this.maxChangeTime;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long listenToAllSectionsAndGetMaxChangeTime() {
|
||||||
|
long maxChangeTime = Long.MIN_VALUE;
|
||||||
|
ArrayList<EntityMovementTrackerSection> notListeningTo = this.sectionsNotListeningTo;
|
||||||
|
for (int i = notListeningTo.size() - 1; i >= 0; i--) {
|
||||||
|
EntityMovementTrackerSection entityMovementTrackerSection = notListeningTo.remove(i);
|
||||||
|
entityMovementTrackerSection.lithium$listenToMovementOnce(this, this.trackedClass);
|
||||||
|
maxChangeTime = Math.max(maxChangeTime, entityMovementTrackerSection.lithium$getChangeTime(this.trackedClass));
|
||||||
|
}
|
||||||
|
return maxChangeTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register(ServerLevel world) {
|
||||||
|
assert world == this.trackedWorldSections.world();
|
||||||
|
|
||||||
|
if (this.timesRegistered == 0) {
|
||||||
|
//noinspection unchecked
|
||||||
|
EntitySectionStorage<E> cache = ((ServerEntityManagerAccessor<E>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
|
||||||
|
|
||||||
|
WorldSectionBox trackedSections = this.trackedWorldSections;
|
||||||
|
int size = trackedSections.numSections();
|
||||||
|
assert size > 0;
|
||||||
|
this.sortedSections = new ArrayList<>(size);
|
||||||
|
this.sectionVisible = new boolean[size];
|
||||||
|
|
||||||
|
//vanilla iteration order in EntitySectionStorage is xzy
|
||||||
|
//WorldSectionBox upper coordinates are exclusive
|
||||||
|
for (int x = trackedSections.chunkX1(); x < trackedSections.chunkX2(); x++) {
|
||||||
|
for (int z = trackedSections.chunkZ1(); z < trackedSections.chunkZ2(); z++) {
|
||||||
|
for (int y = trackedSections.chunkY1(); y < trackedSections.chunkY2(); y++) {
|
||||||
|
EntitySection<E> section = cache.getSection(SectionPos.asLong(x, y, z));
|
||||||
|
EntityMovementTrackerSection sectionAccess = (EntityMovementTrackerSection) section;
|
||||||
|
this.sortedSections.add(section);
|
||||||
|
sectionAccess.lithium$addListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setChanged(world.getGameTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timesRegistered++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unRegister(Level world) {
|
||||||
|
assert world == this.trackedWorldSections.world();
|
||||||
|
if (--this.timesRegistered > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert this.timesRegistered == 0;
|
||||||
|
//noinspection unchecked
|
||||||
|
EntitySectionStorage<E> cache = ((ServerEntityManagerAccessor<E>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
|
||||||
|
((LithiumData) world).lithium$getData().entityMovementTrackers().deleteCanonical(this);
|
||||||
|
|
||||||
|
ArrayList<EntitySection<E>> sections = this.sortedSections;
|
||||||
|
for (int i = sections.size() - 1; i >= 0; i--) {
|
||||||
|
EntitySection<E> section = sections.get(i);
|
||||||
|
EntityMovementTrackerSection sectionAccess = (EntityMovementTrackerSection) section;
|
||||||
|
sectionAccess.lithium$removeListener(cache, this);
|
||||||
|
if (!this.sectionsNotListeningTo.remove(section)) {
|
||||||
|
((EntityMovementTrackerSection) section).lithium$removeListenToMovementOnce(this, this.trackedClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setChanged(world.getGameTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an entity section to this listener, so this listener can look for changes in the section.
|
||||||
|
*/
|
||||||
|
public void onSectionEnteredRange(EntityMovementTrackerSection section) {
|
||||||
|
this.setChanged(this.trackedWorldSections.world().getGameTime());
|
||||||
|
//noinspection SuspiciousMethodCalls
|
||||||
|
int sectionIndex = this.sortedSections.lastIndexOf(section);
|
||||||
|
this.sectionVisible[sectionIndex] = true;
|
||||||
|
|
||||||
|
this.sectionsNotListeningTo.add(section);
|
||||||
|
this.notifyAllListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSectionLeftRange(EntityMovementTrackerSection section) {
|
||||||
|
this.setChanged(this.trackedWorldSections.world().getGameTime());
|
||||||
|
//noinspection SuspiciousMethodCalls
|
||||||
|
int sectionIndex = this.sortedSections.lastIndexOf(section);
|
||||||
|
|
||||||
|
this.sectionVisible[sectionIndex] = false;
|
||||||
|
|
||||||
|
if (!this.sectionsNotListeningTo.remove(section)) {
|
||||||
|
section.lithium$removeListenToMovementOnce(this, this.trackedClass);
|
||||||
|
this.notifyAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that marks that new entities might have appeared or moved in the tracked chunk sections.
|
||||||
|
*/
|
||||||
|
private void setChanged(long atTime) {
|
||||||
|
if (atTime > this.maxChangeTime) {
|
||||||
|
this.maxChangeTime = atTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void listenToEntityMovementOnce(SectionedEntityMovementListener listener) {
|
||||||
|
if (this.sectionedEntityMovementListeners == null) {
|
||||||
|
this.sectionedEntityMovementListeners = new ReferenceOpenHashSet<>();
|
||||||
|
}
|
||||||
|
this.sectionedEntityMovementListeners.add(listener);
|
||||||
|
|
||||||
|
if (!this.sectionsNotListeningTo.isEmpty()) {
|
||||||
|
this.setChanged(this.listenToAllSectionsAndGetMaxChangeTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emitEntityMovement(int classMask, EntityMovementTrackerSection section) {
|
||||||
|
if ((classMask & (1 << this.trackedClass)) != 0) {
|
||||||
|
this.notifyAllListeners();
|
||||||
|
this.sectionsNotListeningTo.add(section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyAllListeners() {
|
||||||
|
ReferenceOpenHashSet<SectionedEntityMovementListener> listeners = this.sectionedEntityMovementListeners;
|
||||||
|
if (listeners != null && !listeners.isEmpty()) {
|
||||||
|
for (SectionedEntityMovementListener listener : listeners) {
|
||||||
|
listener.lithium$handleEntityMovement(this.clazz);
|
||||||
|
}
|
||||||
|
listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.pushable;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
public interface BlockCachingEntity {
|
||||||
|
|
||||||
|
default void lithium$OnBlockCacheDeleted() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default void lithium$OnBlockCacheSet(BlockState newState) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default void lithium$SetClimbingMobCachingSectionUpdateBehavior(boolean listening) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockState lithium$getCachedFeetBlockState();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.entity.pushable;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public abstract class EntityPushablePredicate<S> implements Predicate<S> {
|
||||||
|
public static <T> Predicate<T> and(Predicate<? super T> first, Predicate<? super T> second) {
|
||||||
|
return new EntityPushablePredicate<T>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(T t) {
|
||||||
|
return first.test(t) && second.test(t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.math;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author jafama library
|
||||||
|
* https://github.com/jeffhain/jafama
|
||||||
|
*/
|
||||||
|
public final class FasterMathUtil {
|
||||||
|
private static final int MAX_FLOAT_EXPONENT = 127;
|
||||||
|
private static final int MAX_DOUBLE_EXPONENT = 1023;
|
||||||
|
|
||||||
|
public static int round(float a) {
|
||||||
|
final int bits = Float.floatToRawIntBits(a);
|
||||||
|
final int biasedExp = ((bits >> 23) & 0xFF);
|
||||||
|
final int shift = (23 - 1 + MAX_FLOAT_EXPONENT) - biasedExp;
|
||||||
|
if ((shift & -32) == 0) {
|
||||||
|
int bitsSignum = (((bits >> 31) << 1) + 1);
|
||||||
|
int extendedMantissa = (0x00800000 | (bits & 0x007FFFFF)) * bitsSignum;
|
||||||
|
return ((extendedMantissa >> shift) + 1) >> 1;
|
||||||
|
} else {
|
||||||
|
return (int) a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long round(double a) {
|
||||||
|
final long bits = Double.doubleToRawLongBits(a);
|
||||||
|
final int biasedExp = (((int)(bits >> 52)) & 0x7FF);
|
||||||
|
final int shift = (52 - 1 + MAX_DOUBLE_EXPONENT) - biasedExp;
|
||||||
|
if ((shift & -64) == 0) {
|
||||||
|
long bitsSignum = (((bits >> 63) << 1) + 1);
|
||||||
|
long extendedMantissa = (0x0010000000000000L | (bits & 0x000FFFFFFFFFFFFFL)) * bitsSignum;
|
||||||
|
return ((extendedMantissa >> shift) + 1L) >> 1;
|
||||||
|
} else {
|
||||||
|
return (long) a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float positiveModuloForPositiveIntegerDivisor(float dividend, float divisor) {
|
||||||
|
var modulo = dividend % divisor;
|
||||||
|
return modulo < 0 ? modulo + divisor : modulo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double positiveModuloForPositiveIntegerDivisor(double dividend, double divisor) {
|
||||||
|
var modulo = dividend % divisor;
|
||||||
|
return modulo < 0 ? modulo + divisor : modulo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.reflection;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.ReportedException;
|
||||||
|
import net.minecraft.CrashReport;
|
||||||
|
import net.minecraft.CrashReportCategory;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
public class ReflectionUtil {
|
||||||
|
|
||||||
|
public static boolean hasMethodOverride(Class<?> clazz, Class<?> superclass, boolean fallbackResult, String methodName, Class<?>... methodArgs) {
|
||||||
|
while (clazz != null && clazz != superclass && superclass.isAssignableFrom(clazz)) {
|
||||||
|
try {
|
||||||
|
clazz.getDeclaredMethod(methodName, methodArgs);
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
} catch (NoClassDefFoundError error) {
|
||||||
|
Logger logger = LogManager.getLogger("Lithium Class Analysis");
|
||||||
|
logger.warn("Lithium Class Analysis Error: Class " + clazz.getName() + " cannot be analysed, because" +
|
||||||
|
" getting declared methods crashes with NoClassDefFoundError: " + error.getMessage() +
|
||||||
|
". This is usually caused by modded" +
|
||||||
|
" entities declaring methods that have a return type or parameter type that is annotated" +
|
||||||
|
" with @Environment(value=EnvType.CLIENT). Loading the type is not possible, because" +
|
||||||
|
" it only exists in the CLIENT environment. The recommended fix is to annotate the method with" +
|
||||||
|
" this argument or return type with the same annotation." +
|
||||||
|
" Lithium handles this error by assuming the class cannot be included in some optimizations.");
|
||||||
|
return fallbackResult;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
final String crashedClass = clazz.getName();
|
||||||
|
CrashReport crashReport = CrashReport.forThrowable(e, "Lithium Class Analysis");
|
||||||
|
CrashReportCategory crashReportSection = crashReport.addCategory(e.getClass().toString() + " when getting declared methods.");
|
||||||
|
crashReportSection.setDetail("Analyzed class", crashedClass);
|
||||||
|
crashReportSection.setDetail("Analyzed method name", methodName);
|
||||||
|
crashReportSection.setDetail("Analyzed method args", methodArgs);
|
||||||
|
|
||||||
|
throw new ReportedException(crashReport);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//How to find the remapped methods:
|
||||||
|
//1) Run in the debugger: System.out.println(FabricLoader.getInstance().getMappingResolver().getNamespaceData("intermediary").methodNames)
|
||||||
|
//2) Ctrl+F for the method name, in this case "onEntityCollision". Make sure to find the correct one.
|
||||||
|
// private static final String REMAPPED_ON_ENTITY_COLLISION = FabricLoader.getInstance().getMappingResolver().mapMethodName("intermediary", "net.minecraft.class_4970", "method_9548", "(Lnet/minecraft/class_2680;Lnet/minecraft/class_1937;Lnet/minecraft/class_2338;Lnet/minecraft/class_1297;)V");
|
||||||
|
private static final String REMAPPED_ON_ENTITY_COLLISION = "entityInside";
|
||||||
|
private static final WeakHashMap<Class<?>, Boolean> CACHED_IS_ENTITY_TOUCHABLE = new WeakHashMap<>();
|
||||||
|
public static boolean isBlockStateEntityTouchable(BlockState operand) {
|
||||||
|
Class<? extends Block> blockClazz = operand.getBlock().getClass();
|
||||||
|
//Caching results in hashmap as this calculation takes over a second for all blocks together
|
||||||
|
Boolean result = CACHED_IS_ENTITY_TOUCHABLE.get(blockClazz);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
boolean res = ReflectionUtil.hasMethodOverride(blockClazz, BlockBehaviour.class, true, REMAPPED_ON_ENTITY_COLLISION, BlockState.class, Level.class, BlockPos.class, Entity.class);
|
||||||
|
CACHED_IS_ENTITY_TOUCHABLE.put(blockClazz, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//TODO: There's bunch of client sided options, we need to safely remove them if possible if not keep the behaviour since mixins work regardless of client or not
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.util;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-initialized constants to avoid unnecessary allocations.
|
||||||
|
*/
|
||||||
|
public final class EquipmentSlotConstants {
|
||||||
|
private EquipmentSlotConstants() {}
|
||||||
|
|
||||||
|
public static final EquipmentSlot[] ALL = EquipmentSlot.values();
|
||||||
|
}
|
||||||
@@ -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,9 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world;
|
||||||
|
|
||||||
|
//import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
//
|
||||||
|
//public interface ChunkAwareEntityIterable<T extends EntityAccess> {
|
||||||
|
// Iterable<T> lithium$IterateEntitiesInTrackedSections();
|
||||||
|
//}
|
||||||
|
|
||||||
|
//PersistentEntitySectionManager does not work on paper
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public interface ChunkView {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
ChunkAccess lithium$getLoadedChunk(int chunkX, int chunkZ);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.entity.pushable.BlockCachingEntity;
|
||||||
|
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
|
||||||
|
import net.minecraft.util.AbortableIterationConsumer;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public interface ClimbingMobCachingSection {
|
||||||
|
AbortableIterationConsumer.Continuation lithium$collectPushableEntities(Level world, Entity except, AABB box, EntityPushablePredicate<? super Entity> entityPushablePredicate, ArrayList<Entity> entities);
|
||||||
|
|
||||||
|
void lithium$onEntityModifiedCachedBlock(BlockCachingEntity entity, BlockState newBlockState);
|
||||||
|
}
|
||||||
@@ -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.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().lookup(Registries.BANNER_PATTERN).map(Raid::getLeaderBannerInstance).orElse(null),
|
||||||
|
new ReferenceOpenHashSet<>(),
|
||||||
|
new LithiumInterner<>(),
|
||||||
|
new LithiumInterner<>(),
|
||||||
|
new Long2ReferenceOpenHashMap<>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LithiumData.Data lithium$getData();
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
|
||||||
|
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.ClientEntityManagerAccessor;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.EntityTrackingSectionAccessor;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerEntityManagerAccessor;
|
||||||
|
import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.accessors.ServerWorldAccessor;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.util.ClassInstanceMultiMap;
|
||||||
|
import net.minecraft.util.AbortableIterationConsumer;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.level.EntityGetter;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class WorldHelper {
|
||||||
|
public static final boolean CUSTOM_TYPE_FILTERABLE_LIST_DISABLED = !ClassGroupFilterableList.class.isAssignableFrom(ClassInstanceMultiMap.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partial [VanillaCopy]
|
||||||
|
* The returned entity iterator is only used for collision interactions. As most entities do not collide with other
|
||||||
|
* entities (cramming is different), getting them is not necessary. This is why we only get entities when they override
|
||||||
|
* {@link Entity#canBeCollidedWith()} if the reference entity does not override {@link Entity#canCollideWith(Entity)}.
|
||||||
|
* Note that the returned iterator contains entities that override these methods. This does not mean that these methods
|
||||||
|
* always return true.
|
||||||
|
*
|
||||||
|
* @param entityView the world
|
||||||
|
* @param box the box the entities have to collide with
|
||||||
|
* @param collidingEntity the entity that is searching for the colliding entities
|
||||||
|
* @return iterator of entities with collision boxes
|
||||||
|
*/
|
||||||
|
public static List<Entity> getEntitiesForCollision(EntityGetter entityView, AABB box, Entity collidingEntity) {
|
||||||
|
if (!CUSTOM_TYPE_FILTERABLE_LIST_DISABLED && entityView instanceof Level world && (collidingEntity == null || !EntityClassGroup.CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(collidingEntity.getClass()))) {
|
||||||
|
EntitySectionStorage<Entity> cache = getEntityCacheOrNull(world);
|
||||||
|
if (cache != null) {
|
||||||
|
world.getProfiler().incrementCounter("getEntities");
|
||||||
|
return getEntitiesOfClassGroup(cache, collidingEntity, EntityClassGroup.NoDragonClassGroup.BOAT_SHULKER_LIKE_COLLISION, box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//use vanilla code in case the shortcut is not applicable
|
||||||
|
// due to the reference entity implementing special collision or the mixin being disabled in the config
|
||||||
|
return entityView.getEntities(collidingEntity, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> getOtherEntitiesForCollision(EntityGetter entityView, AABB box, @Nullable Entity collidingEntity, Predicate<? super Entity> entityPredicate) {
|
||||||
|
if (!CUSTOM_TYPE_FILTERABLE_LIST_DISABLED && entityView instanceof Level world) {
|
||||||
|
if (collidingEntity == null || !EntityClassGroup.CUSTOM_COLLIDE_LIKE_MINECART_BOAT_WINDCHARGE.contains(collidingEntity.getClass())) {
|
||||||
|
EntitySectionStorage<Entity> cache = getEntityCacheOrNull(world);
|
||||||
|
if (cache != null) {
|
||||||
|
world.getProfiler().incrementCounter("getEntities");
|
||||||
|
return getEntitiesOfClassGroup(cache, collidingEntity, EntityClassGroup.NoDragonClassGroup.BOAT_SHULKER_LIKE_COLLISION, box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//use vanilla code in case the shortcut is not applicable
|
||||||
|
// due to the reference entity implementing special collision or the mixin being disabled in the config
|
||||||
|
return entityView.getEntities(collidingEntity, box, entityPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Requires util.accessors
|
||||||
|
public static EntitySectionStorage<Entity> getEntityCacheOrNull(Level world) {
|
||||||
|
// Does not work on Paper...
|
||||||
|
// if (world instanceof ServerWorldAccessor) {
|
||||||
|
// //noinspection unchecked
|
||||||
|
// return ((ServerEntityManagerAccessor<Entity>) ((ServerWorldAccessor) world).getEntityManager()).getSectionStorage();
|
||||||
|
// }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> getEntitiesOfClassGroup(EntitySectionStorage<Entity> cache, Entity collidingEntity, EntityClassGroup.NoDragonClassGroup entityClassGroup, AABB box) {
|
||||||
|
ArrayList<Entity> entities = new ArrayList<>();
|
||||||
|
cache.forEachAccessibleNonEmptySection(box, section -> {
|
||||||
|
//noinspection unchecked
|
||||||
|
ClassInstanceMultiMap<Entity> allEntities = ((EntityTrackingSectionAccessor<Entity>) section).getStorage();
|
||||||
|
//noinspection unchecked
|
||||||
|
Collection<Entity> entitiesOfType = ((ClassGroupFilterableList<Entity>) allEntities).lithium$getAllOfGroupType(entityClassGroup);
|
||||||
|
if (!entitiesOfType.isEmpty()) {
|
||||||
|
for (Entity entity : entitiesOfType) {
|
||||||
|
if (entity.getBoundingBox().intersects(box) && !entity.isSpectator() && entity != collidingEntity) {
|
||||||
|
//skip the dragon piece check without issues by only allowing EntityClassGroup.NoDragonClassGroup as type
|
||||||
|
entities.add(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AbortableIterationConsumer.Continuation.CONTINUE;
|
||||||
|
});
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Entity> getPushableEntities(Level world, EntitySectionStorage<Entity> cache, Entity except, AABB box, EntityPushablePredicate<? super Entity> entityPushablePredicate) {
|
||||||
|
ArrayList<Entity> entities = new ArrayList<>();
|
||||||
|
cache.forEachAccessibleNonEmptySection(box, section -> ((ClimbingMobCachingSection) section).lithium$collectPushableEntities(world, except, box, entityPushablePredicate, entities));
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean areNeighborsWithinSameChunk(BlockPos pos) {
|
||||||
|
int localX = pos.getX() & 15;
|
||||||
|
int localZ = pos.getZ() & 15;
|
||||||
|
|
||||||
|
return localX > 0 && localZ > 0 && localX < 15 && localZ < 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean areNeighborsWithinSameChunkSection(int x, int y, int z) {
|
||||||
|
int localX = x & 15;
|
||||||
|
int localY = y & 15;
|
||||||
|
int localZ = z & 15;
|
||||||
|
|
||||||
|
return localX > 0 && localY > 0 && localZ > 0 && localX < 15 && localY < 15 && localZ < 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean arePosWithinSameChunk(BlockPos pos1, BlockPos pos2) {
|
||||||
|
return pos1.getX() >> 4 == pos2.getX() >> 4 && pos1.getZ() >> 4 == pos2.getZ() >> 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world.blockentity;
|
||||||
|
|
||||||
|
public interface SupportCache {
|
||||||
|
boolean lithium$isSupported();
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world.chunk;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.FullChunkStatus;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class ChunkStatusTracker {
|
||||||
|
|
||||||
|
//Add other callback types in the future when needed
|
||||||
|
private static final ArrayList<BiConsumer<ServerLevel, ChunkPos>> UNLOAD_CALLBACKS = new ArrayList<>();
|
||||||
|
public static void onChunkStatusChange(ServerLevel serverWorld, ChunkPos pos, FullChunkStatus levelType) {
|
||||||
|
boolean loaded = levelType.isOrAfter(FullChunkStatus.FULL);
|
||||||
|
if (!loaded) {
|
||||||
|
for (int i = 0; i < UNLOAD_CALLBACKS.size(); i++) {
|
||||||
|
UNLOAD_CALLBACKS.get(i).accept(serverWorld, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void registerUnloadCallback(BiConsumer<ServerLevel, ChunkPos> callback) {
|
||||||
|
UNLOAD_CALLBACKS.add(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world.chunk;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public interface ClassGroupFilterableList<T> {
|
||||||
|
Collection<T> lithium$getAllOfGroupType(EntityClassGroup type);
|
||||||
|
}
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.common.world.chunk;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import it.unimi.dsi.fastutil.HashCommon;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.needs_testing.chunk.palette.PaletteResizeAccessor;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.VarInt;
|
||||||
|
import net.minecraft.core.IdMap;
|
||||||
|
import net.minecraft.world.level.chunk.MissingPaletteEntryException;
|
||||||
|
import net.minecraft.world.level.chunk.Palette;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static it.unimi.dsi.fastutil.Hash.FAST_LOAD_FACTOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generally provides better performance over the vanilla {@link net.minecraft.world.level.chunk.HashMapPalette} when calling
|
||||||
|
* {@link LithiumHashPalette#idFor(Object)} through using a faster backing map and reducing pointer chasing.
|
||||||
|
*/
|
||||||
|
public class LithiumHashPalette<T> implements Palette<T> {
|
||||||
|
private static final int ABSENT_VALUE = -1;
|
||||||
|
|
||||||
|
private final IdMap<T> idList;
|
||||||
|
// private final PaletteResize<T> resizeHandler;
|
||||||
|
private final PaletteResizeAccessor<T> resizeHandler;
|
||||||
|
private final int indexBits;
|
||||||
|
|
||||||
|
private final Reference2IntMap<T> table;
|
||||||
|
private T[] entries;
|
||||||
|
private int size = 0;
|
||||||
|
|
||||||
|
public LithiumHashPalette(IdMap<T> idList, PaletteResizeAccessor<T> resizeHandler, int indexBits, T[] entries, Reference2IntMap<T> table, int size) {
|
||||||
|
this.idList = idList;
|
||||||
|
this.resizeHandler = resizeHandler;
|
||||||
|
this.indexBits = indexBits;
|
||||||
|
this.entries = entries;
|
||||||
|
this.table = table;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResizeAccessor<T> resizeHandler, List<T> list) {
|
||||||
|
this(idList, bits, resizeHandler);
|
||||||
|
|
||||||
|
for (T t : list) {
|
||||||
|
this.addEntry(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public LithiumHashPalette(IdMap<T> idList, int bits, PaletteResizeAccessor<T> resizeHandler) {
|
||||||
|
this.idList = idList;
|
||||||
|
this.indexBits = bits;
|
||||||
|
this.resizeHandler = resizeHandler;
|
||||||
|
|
||||||
|
int capacity = 1 << bits;
|
||||||
|
|
||||||
|
this.entries = (T[]) new Object[capacity];
|
||||||
|
this.table = new Reference2IntOpenHashMap<>(capacity, FAST_LOAD_FACTOR);
|
||||||
|
this.table.defaultReturnValue(ABSENT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int idFor(T obj) {
|
||||||
|
int id = this.table.getInt(obj);
|
||||||
|
|
||||||
|
if (id == ABSENT_VALUE) {
|
||||||
|
id = this.computeEntry(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean maybeHas(Predicate<T> predicate) {
|
||||||
|
for (int i = 0; i < this.size; ++i) {
|
||||||
|
if (predicate.test(this.entries[i])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int computeEntry(T obj) {
|
||||||
|
int id = this.addEntry(obj);
|
||||||
|
|
||||||
|
if (id >= 1 << this.indexBits) {
|
||||||
|
if (this.resizeHandler == null) {
|
||||||
|
throw new IllegalStateException("Cannot grow");
|
||||||
|
} else {
|
||||||
|
id = this.resizeHandler.callOnResize(this.indexBits + 1, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int addEntry(T obj) {
|
||||||
|
int nextId = this.size;
|
||||||
|
|
||||||
|
if (nextId >= this.entries.length) {
|
||||||
|
this.resize(this.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.table.put(obj, nextId);
|
||||||
|
this.entries[nextId] = obj;
|
||||||
|
|
||||||
|
this.size++;
|
||||||
|
|
||||||
|
return nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resize(int neededCapacity) {
|
||||||
|
this.entries = Arrays.copyOf(this.entries, HashCommon.nextPowerOfTwo(neededCapacity + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull T valueFor(int id) {
|
||||||
|
T[] entries = this.entries;
|
||||||
|
|
||||||
|
T entry = null;
|
||||||
|
if (id >= 0 && id < entries.length) {
|
||||||
|
entry = entries[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry != null) {
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
throw new MissingPaletteEntryException(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void read(FriendlyByteBuf buf) {
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
int entryCount = buf.readVarInt();
|
||||||
|
|
||||||
|
for (int i = 0; i < entryCount; ++i) {
|
||||||
|
this.addEntry(this.idList.byId(buf.readVarInt()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(FriendlyByteBuf buf) {
|
||||||
|
int size = this.size;
|
||||||
|
buf.writeVarInt(size);
|
||||||
|
|
||||||
|
for (int i = 0; i < size; ++i) {
|
||||||
|
buf.writeVarInt(this.idList.getId(this.valueFor(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSize() {
|
||||||
|
int size = VarInt.getByteSize(this.size);
|
||||||
|
|
||||||
|
for (int i = 0; i < this.size; ++i) {
|
||||||
|
size += VarInt.getByteSize(this.idList.getId(this.valueFor(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Palette<T> copy() {
|
||||||
|
return new LithiumHashPalette<>(this.idList, this.resizeHandler, this.indexBits, this.entries.clone(), new Reference2IntOpenHashMap<>(this.table), this.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clear() {
|
||||||
|
Arrays.fill(this.entries, null);
|
||||||
|
this.table.clear();
|
||||||
|
this.size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> getElements() {
|
||||||
|
ImmutableList.Builder<T> builder = new ImmutableList.Builder<>();
|
||||||
|
for (T entry : this.entries) {
|
||||||
|
if (entry != null) {
|
||||||
|
builder.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <A> Palette<A> create(int bits, IdMap<A> idList, PaletteResizeAccessor<A> listener, List<A> list) {
|
||||||
|
return new LithiumHashPalette<>(idList, bits, listener, list);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.entity.EntityTickList;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Mixin(EntityTickList.class)
|
||||||
|
public class MixinEntityTickList {
|
||||||
|
@Unique
|
||||||
|
private Int2ObjectLinkedOpenHashMap<Entity> nitori$entities = new Int2ObjectLinkedOpenHashMap<>();
|
||||||
|
@Unique
|
||||||
|
private Int2ObjectLinkedOpenHashMap<Entity> nitori$iteratorPointer = null;
|
||||||
|
|
||||||
|
// I've decided I hate Paper's design.
|
||||||
|
// All of these are effectively Overwrites.
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void nitori$ensureActiveIsNotIterated() {
|
||||||
|
if (nitori$iteratorPointer == nitori$entities) {
|
||||||
|
// Avoid a ConcurrentModificationException by cloning the map before modifying it.
|
||||||
|
// Side effect is that it allocates more memory to avoid blocking the main thread, but it's all pointers anyway.
|
||||||
|
nitori$entities = nitori$entities.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "add", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void add(Entity entity, CallbackInfo ci) {
|
||||||
|
nitori$ensureActiveIsNotIterated();
|
||||||
|
nitori$entities.put(entity.getId(), entity);
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "remove", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void remove(Entity entity, CallbackInfo ci) {
|
||||||
|
nitori$ensureActiveIsNotIterated();
|
||||||
|
nitori$entities.remove(entity.getId());
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "contains", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void contains(Entity entity, CallbackInfoReturnable<Boolean> ci) {
|
||||||
|
ci.setReturnValue(nitori$entities.containsKey(entity.getId()));
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "forEach", at = @At("HEAD"), cancellable = true)
|
||||||
|
public void forEach(Consumer<Entity> action, CallbackInfo ci) {
|
||||||
|
if (nitori$iteratorPointer == nitori$entities) {
|
||||||
|
nitori$entities = nitori$entities.clone(); // Avoid a ConcurrentModificationException by cloning the map before iterating over it.
|
||||||
|
}
|
||||||
|
|
||||||
|
nitori$iteratorPointer = nitori$entities; // Mark the map as being iterated.
|
||||||
|
|
||||||
|
try {
|
||||||
|
nitori$iteratorPointer.values().forEach(action); // Iterate over the map.
|
||||||
|
} finally {
|
||||||
|
nitori$iteratorPointer = null; // Mark the map as no longer being iterated.
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.ai.pathing;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.pathfinder.PathfindingContext;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(PathfindingContext.class)
|
||||||
|
public interface PathContextAccessor {
|
||||||
|
@Accessor
|
||||||
|
BlockPos.MutableBlockPos getMutablePos();
|
||||||
|
}
|
||||||
@@ -1,48 +1,46 @@
|
|||||||
package net.gensokyoreimagined.nitori.mixin.alloc.composter;
|
package net.gensokyoreimagined.nitori.mixin.alloc.composter;
|
||||||
|
|
||||||
//import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
|
import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
|
||||||
//import net.minecraft.world.WorldlyContainer;
|
import net.minecraft.world.WorldlyContainer;
|
||||||
//import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
//import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
//import org.spongepowered.asm.mixin.Overwrite;
|
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;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//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,40 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.collections.attributes;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.AttributeMap;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.Attribute;
|
||||||
|
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||||
|
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.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Mixin(AttributeMap.class)
|
||||||
|
public class AttributeContainerMixin {
|
||||||
|
@Mutable
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Map<Attribute, AttributeInstance> attributes;
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Set<AttributeInstance> dirtyAttributes;
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "<init>",
|
||||||
|
at = @At("RETURN")
|
||||||
|
)
|
||||||
|
private void initCollections(AttributeSupplier defaultAttributes, CallbackInfo ci) {
|
||||||
|
this.attributes = new Reference2ReferenceOpenHashMap<>(0);
|
||||||
|
this.dirtyAttributes = new ReferenceOpenHashSet<>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,52 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.collections.chunk_tickets;
|
||||||
|
|
||||||
|
import net.minecraft.util.SortedArraySet;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@Mixin(SortedArraySet.class)
|
||||||
|
public abstract class SortedArraySetMixin<T> implements Collection<T> {
|
||||||
|
@Shadow
|
||||||
|
int size;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
T[] contents ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an optimized implementation of {@link Collection#removeIf(Predicate)} which doesn't attempt to shift
|
||||||
|
* the values in the array multiple times with each removal. This also eliminates a number of object allocations
|
||||||
|
* and works on the direct backing array, speeding things up a fair chunk.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean removeIf(Predicate<? super T> filter) {
|
||||||
|
T[] arr = this.contents ;
|
||||||
|
|
||||||
|
int writeLim = this.size;
|
||||||
|
int writeIdx = 0;
|
||||||
|
|
||||||
|
for (int readIdx = 0; readIdx < writeLim; readIdx++) {
|
||||||
|
T obj = arr[readIdx];
|
||||||
|
|
||||||
|
// If the filter does not pass the object, simply skip over it. The write pointer will
|
||||||
|
// not be advanced and the next element to pass will instead take this one's place.
|
||||||
|
if (filter.test(obj)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the read and write pointers are the same, then no removals have occurred so far. This
|
||||||
|
// allows us to skip copying unchanged values back into the array.
|
||||||
|
if (writeIdx != readIdx) {
|
||||||
|
arr[writeIdx] = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.size = writeIdx;
|
||||||
|
|
||||||
|
return writeLim != writeIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,33 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.collections.goals;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
|
||||||
|
import net.minecraft.world.entity.ai.goal.GoalSelector;
|
||||||
|
import net.minecraft.world.entity.ai.goal.WrappedGoal;
|
||||||
|
import net.minecraft.util.profiling.ProfilerFiller;
|
||||||
|
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.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Mixin(GoalSelector.class)
|
||||||
|
public abstract class GoalSelectorMixin {
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Set<WrappedGoal> availableGoals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the goal set with an optimized collection type which performs better for iteration.
|
||||||
|
*/
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void reinit(Supplier<ProfilerFiller> supplier, CallbackInfo ci) {
|
||||||
|
this.availableGoals = new ObjectLinkedOpenHashSet<>(this.availableGoals);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.collections.mob_spawning;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.MobCategory;
|
||||||
|
import net.minecraft.util.random.WeightedRandomList;
|
||||||
|
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||||
|
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.Map;
|
||||||
|
|
||||||
|
@Mixin(MobSpawnSettings.class)
|
||||||
|
public class SpawnSettingsMixin {
|
||||||
|
@Mutable
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private Map<MobCategory, WeightedRandomList<MobSpawnSettings.SpawnerData>> spawners;
|
||||||
|
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void reinit(float creatureSpawnProbability, Map<MobCategory, WeightedRandomList<MobSpawnSettings.SpawnerData>> spawners, Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> spawnCosts, CallbackInfo ci) {
|
||||||
|
Map<MobCategory, WeightedRandomList<MobSpawnSettings.SpawnerData>> spawns = Maps.newEnumMap(MobCategory.class);
|
||||||
|
|
||||||
|
for (Map.Entry<MobCategory, WeightedRandomList<MobSpawnSettings.SpawnerData>> entry : this.spawners.entrySet()) {
|
||||||
|
spawns.put(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.spawners = spawns;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.entity.fall_damage;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
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(Entity.class)
|
||||||
|
public abstract class NoFallDamageMixin {
|
||||||
|
|
||||||
|
@Shadow public abstract boolean onGround();
|
||||||
|
|
||||||
|
@Shadow public abstract void resetFallDistance();
|
||||||
|
|
||||||
|
|
||||||
|
@Inject(method = "checkFallDamage", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void cancelFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition, CallbackInfo ci) {
|
||||||
|
if (!(((Entity)(Object)this) instanceof LivingEntity)) {
|
||||||
|
if (this.onGround()) {
|
||||||
|
this.resetFallDistance();
|
||||||
|
}
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.entity.fast_hand_swing;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
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(LivingEntity.class)
|
||||||
|
public abstract class LivingEntityMixin {
|
||||||
|
@Shadow
|
||||||
|
public boolean swinging;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public int swingTime;
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "updateSwingTime",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true
|
||||||
|
)
|
||||||
|
private void skipGetDuration(CallbackInfo ci) {
|
||||||
|
if (!this.swinging && this.swingTime == 0) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.entity.sprinting_particles;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
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.Redirect;
|
||||||
|
|
||||||
|
@Mixin(Entity.class)
|
||||||
|
public abstract class EntityMixin {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract Level level();
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "baseTick",
|
||||||
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;canSpawnSprintParticle()Z")
|
||||||
|
)
|
||||||
|
private boolean skipParticlesOnServerSide(Entity instance) {
|
||||||
|
if (instance.level().isClientSide()) {
|
||||||
|
return instance.canSpawnSprintParticle();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.logic.fast_bits_blockpos;
|
||||||
|
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(SectionPos.class)
|
||||||
|
public interface ChunkSectionPosAccessor {
|
||||||
|
@Invoker("<init>")
|
||||||
|
public static SectionPos invokeChunkSectionPos(int i, int j, int k) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.logic.fast_bits_blockpos;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credit to PaperMC Patch #0421
|
||||||
|
*/
|
||||||
|
@Mixin(BlockPos.class)
|
||||||
|
public class OptimizedBlockPosBitsMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static long offset(long value, int x, int y, int z) {
|
||||||
|
return asLong((int) (value >> 38) + x, (int) ((value << 52) >> 52) + y, (int) ((value << 26) >> 38) + z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int getX(long packedPos) {
|
||||||
|
return (int) (packedPos >> 38);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int getY(long packedPos) {
|
||||||
|
return (int) ((packedPos << 52) >> 52);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int getZ(long packedPos) {
|
||||||
|
return (int) ((packedPos << 26) >> 38);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static BlockPos of(long packedPos) {
|
||||||
|
return new BlockPos((int) (packedPos >> 38), (int) ((packedPos << 52) >> 52), (int) ((packedPos << 26) >> 38));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Inline
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static long asLong(int x, int y, int z) {
|
||||||
|
return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.logic.fast_bits_blockpos;
|
||||||
|
|
||||||
|
//TODO: impl this
|
||||||
|
|
||||||
|
//import net.minecraft.core.BlockPos;
|
||||||
|
//import net.minecraft.world.level.ChunkPos;
|
||||||
|
//import net.minecraft.core.SectionPos;
|
||||||
|
//import net.minecraft.core.Vec3i;
|
||||||
|
//import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
//import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
//import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
//
|
||||||
|
//import java.util.stream.Stream;
|
||||||
|
//
|
||||||
|
//@Mixin(SectionPos.class)
|
||||||
|
//public abstract class OptimizedChunkSecPosBitsMixin extends Vec3i {
|
||||||
|
// @Shadow
|
||||||
|
// public static Stream<SectionPos> betweenClosedStream(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// public OptimizedChunkSecPosBitsMixin(int x, int y, int z) {
|
||||||
|
// super(x, y, z);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static SectionPos of(BlockPos pos) {
|
||||||
|
// return ChunkSectionPosAccessor.invokeChunkSectionPos(pos.getX() >> 4, pos.getY() >> 4, pos.getZ() >> 4);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static SectionPos of(long packed) {
|
||||||
|
// return ChunkSectionPosAccessor.invokeChunkSectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static long offset(long packed, int x, int y, int z) {
|
||||||
|
// return (((long) ((int) (packed >> 42) + x) & 4194303L) << 42) | (((long) ((int) (packed << 44 >> 44) + y) & 1048575L)) | (((long) ((int) (packed << 22 >> 42) + z) & 4194303L) << 20);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static short sectionRelativePos(BlockPos pos) {
|
||||||
|
// return (short) ((pos.getX() & 15) << 8 | (pos.getZ() & 15) << 4 | pos.getY() & 15);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public final int minBlockX() {
|
||||||
|
// return this.getX() << 4;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public int getMinY() {
|
||||||
|
// return this.getY() << 4;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public int getMinZ() {
|
||||||
|
// return this.getZ() << 4;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static long blockToSection(long blockPos) {
|
||||||
|
// return (((long) (int) (blockPos >> 42) & 4194303L) << 42) | (((long) (int) ((blockPos << 52) >> 56) & 1048575L)) | (((long) (int) ((blockPos << 26) >> 42) & 4194303L) << 20);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static long asLong(int x, int y, int z) {
|
||||||
|
// return (((long) x & 4194303L) << 42) | (((long) y & 1048575L)) | (((long) z & 4194303L) << 20);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public long asLong() {
|
||||||
|
// return (((long) getX() & 4194303L) << 42) | (((long) getY() & 1048575L)) | (((long) getZ() & 4194303L) << 20);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static Stream<SectionPos> betweenClosedStream(SectionPos center, int radius) {
|
||||||
|
// return betweenClosedStream(center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @author QPCrummer
|
||||||
|
// * @reason Inline
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static Stream<SectionPos> betweenClosedStream(ChunkPos center, int radius, int minY, int maxY) {
|
||||||
|
// return betweenClosedStream(center.x - radius, 0, center.z - radius, center.x + radius, 15, center.z + radius);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
@@ -1,38 +1,37 @@
|
|||||||
package net.gensokyoreimagined.nitori.mixin.math.fast_util;
|
package net.gensokyoreimagined.nitori.mixin.math.fast_util;
|
||||||
|
|
||||||
//import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
//import net.minecraft.core.Direction.Axis;
|
import net.minecraft.util.RandomSource;
|
||||||
//import net.minecraft.util.RandomSource;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
//import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
//import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
//import org.spongepowered.asm.mixin.Overwrite;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
//import org.spongepowered.asm.mixin.Shadow;
|
|
||||||
//
|
@Mixin(Direction.class)
|
||||||
//@Mixin(Direction.class)
|
public class DirectionMixin {
|
||||||
//public class DirectionMixin {
|
@Shadow
|
||||||
// @Shadow
|
@Final
|
||||||
// @Final
|
private static Direction[] VALUES;
|
||||||
// private static Direction[] ALL;
|
|
||||||
//
|
@Shadow
|
||||||
// @Shadow
|
@Final
|
||||||
// @Final
|
private int oppositeIndex;
|
||||||
// private int idOpposite;
|
|
||||||
//
|
/**
|
||||||
// /**
|
* @reason Avoid the modulo/abs operations
|
||||||
// * @reason Avoid the modulo/abs operations
|
* @author JellySquid
|
||||||
// * @author JellySquid
|
*/
|
||||||
// */
|
@Overwrite
|
||||||
// @Overwrite
|
public Direction getOpposite() {
|
||||||
// public Direction getOpposite() {
|
return VALUES[this.oppositeIndex];
|
||||||
// return ALL[this.idOpposite];
|
}
|
||||||
// }
|
|
||||||
//
|
/**
|
||||||
// /**
|
* @reason Do not allocate an excessive number of Direction arrays
|
||||||
// * @reason Do not allocate an excessive number of Direction arrays
|
* @author JellySquid
|
||||||
// * @author JellySquid
|
*/
|
||||||
// */
|
@Overwrite
|
||||||
// @Overwrite
|
public static Direction getRandom(RandomSource rand) {
|
||||||
// public static Direction getRandom(RandomSource rand) {
|
return VALUES[rand.nextInt(VALUES.length)];
|
||||||
// return Direction.ALL[rand.nextInt(ALL.length)];
|
}
|
||||||
// }
|
}
|
||||||
//}
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.math.general;
|
||||||
|
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@Mixin(Mth.class)
|
||||||
|
public class GenericFastMathMixin {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Slightly more optimized
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static boolean rayIntersectsAABB(Vec3 origin, Vec3 direction, AABB box) {
|
||||||
|
|
||||||
|
double d = (box.minX + box.maxX) * 0.5;
|
||||||
|
double e = (box.maxX - box.minX) * 0.5;
|
||||||
|
double f = origin.x - d;
|
||||||
|
if (Math.abs(f) > e && f * direction.x > 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double g = (box.minY + box.maxY) * 0.5;
|
||||||
|
double h = (box.maxY - box.minY) * 0.5;
|
||||||
|
double i = origin.y - g;
|
||||||
|
if (Math.abs(i) > h && i * direction.y >= 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double j = (box.minZ + box.maxZ) * 0.5;
|
||||||
|
double k = (box.maxZ - box.minZ) * 0.5;
|
||||||
|
double l = origin.z - j;
|
||||||
|
if (Math.abs(l) > k && l * direction.z >= 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double m = Math.abs(direction.x);
|
||||||
|
double n = Math.abs(direction.y);
|
||||||
|
double o = Math.abs(direction.z);
|
||||||
|
double p = direction.y * l - direction.z * i;
|
||||||
|
if (Math.abs(p) > h * o + k * n || Math.abs(direction.z * f - direction.x * l) > e * o + k * m) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.abs(direction.x * i - direction.y * f) < e * n + h * m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Slightly more optimized
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static IntStream outFromOrigin(int seed, int lowerBound, int upperBound, int steps) {
|
||||||
|
|
||||||
|
if (steps < 1 || seed < lowerBound || seed > upperBound) {
|
||||||
|
return IntStream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return IntStream.iterate(seed, i -> {
|
||||||
|
int nextValue = i + (i <= seed ? steps : -steps);
|
||||||
|
return nextValue >= lowerBound && nextValue <= upperBound ? nextValue : i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.math.intrinsic;
|
||||||
|
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
// Credit to Gale patch #0018
|
||||||
|
@Mixin(Mth.class)
|
||||||
|
public class MathHelperIntrinsicMixin {
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Use Intrinsic instead
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int floor(float value) {
|
||||||
|
return (int) Math.floor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Use Intrinsic instead
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int floor(double value) {
|
||||||
|
return (int) Math.floor(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Use Intrinsic instead
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int ceil(float value) {
|
||||||
|
return (int) Math.ceil(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Use Intrinsic instead
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int ceil(double value) {
|
||||||
|
return (int) Math.ceil(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Use Intrinsic instead
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static double absMax(double a, double b) {
|
||||||
|
return Math.max(Math.abs(a), Math.abs(b));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.math.joml;
|
||||||
|
|
||||||
|
import org.joml.Math;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(value = Math.class, remap = false)
|
||||||
|
public class JOMLMixin {
|
||||||
|
@Redirect(method = "<clinit>", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMath() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<clinit>", at = @At(value = "FIELD", target = "Lorg/joml/Options;SIN_LOOKUP:Z"))
|
||||||
|
private static boolean redirectSinLookup() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "sin(F)F", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup1() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "sin(F)F", at = @At(value = "FIELD", target = "Lorg/joml/Options;SIN_LOOKUP:Z"))
|
||||||
|
private static boolean redirectSinLookup1() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "sin(D)D", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup2() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "sin(D)D", at = @At(value = "FIELD", target = "Lorg/joml/Options;SIN_LOOKUP:Z"))
|
||||||
|
private static boolean redirectSinLookup2() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "cos(F)F", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup3() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "cos(D)D", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup4() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "cosFromSin(FF)F", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup5() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "cosFromSin(DD)D", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup6() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "atan2(DD)D", at = @At(value = "FIELD", target = "Lorg/joml/Options;FASTMATH:Z"))
|
||||||
|
private static boolean redirectFastMathLookup7() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.math.rounding;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.math.FasterMathUtil;
|
||||||
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(value = Shapes.class, priority = 1010)
|
||||||
|
public class FastRoundingVoxShapeMixin {
|
||||||
|
@Redirect(
|
||||||
|
method = "create(DDDDDD)Lnet/minecraft/world/phys/shapes/VoxelShape;",
|
||||||
|
require = 0,
|
||||||
|
at = @At(value = "INVOKE", target = "Ljava/lang/Math;round(D)J"))
|
||||||
|
private static long fasterRoundCuboid(double value) {
|
||||||
|
return FasterMathUtil.round(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "findBits",
|
||||||
|
require = 0,
|
||||||
|
at = @At(value = "INVOKE", target = "Ljava/lang/Math;round(D)J"))
|
||||||
|
private static long fasterRoundResolution(double value) {
|
||||||
|
return FasterMathUtil.round(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// 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 net.minecraft.util.Mth;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.math.vec;
|
||||||
|
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
|
||||||
|
@Mixin(Vec3.class)
|
||||||
|
public class FastMathVec3DMixin {
|
||||||
|
@Mutable
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
public double x;
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow @Final public double y;
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow @Final public double z;
|
||||||
|
private Vec3 cachedNormalized;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author QPCrummer
|
||||||
|
* @reason Cache normalized Vec
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public Vec3 normalize() {
|
||||||
|
if (cachedNormalized == null) {
|
||||||
|
double squaredLength = x * x + y * y + z * z;
|
||||||
|
if (squaredLength < 1.0E-8) {
|
||||||
|
cachedNormalized = Vec3.ZERO;
|
||||||
|
} else {
|
||||||
|
double invLength = 1.0 / Math.sqrt(squaredLength);
|
||||||
|
cachedNormalized = new Vec3(x * invLength, y * invLength, z * invLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedNormalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
package net.gensokyoreimagined.nitori.mixin;
|
package net.gensokyoreimagined.nitori.mixin.needs_testing;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import net.gensokyoreimagined.nitori.common.util.collections.HashedReferenceList;
|
import net.gensokyoreimagined.nitori.common.util.collections.HashedReferenceList;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.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,10 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.chunk.palette;
|
||||||
|
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.world.level.chunk.PaletteResize")
|
||||||
|
public abstract class PaletteResizeAccessor<T> {
|
||||||
|
@Invoker
|
||||||
|
public abstract int callOnResize(int newBits, T object);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.chunk.palette;
|
||||||
|
|
||||||
|
// import net.minecraft.core.IdMap;
|
||||||
|
// import net.minecraft.world.level.chunk.Palette;
|
||||||
|
// import net.minecraft.world.level.chunk.PaletteResize;
|
||||||
|
// import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
// import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
// import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
//
|
||||||
|
// @Mixin(targets = "net.minecraft.world.level.chunk.PalettedContainer.Configuration")
|
||||||
|
// public abstract class PalettedContainerConfigurationMixin<T> {
|
||||||
|
// @Invoker("<init>(Lnet/minecraft/world/level/chunk/Palette$Factory;I)V")
|
||||||
|
// public static <T> PalettedContainerConfigurationMixin<T> create(Palette.Factory factory, int bits) {
|
||||||
|
// throw new AssertionError("mukyu~!");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Invoker
|
||||||
|
// public abstract PalettedContainer.Data<T> createData(IdMap<T> idList, PaletteResize<T> listener, int size);
|
||||||
|
// }
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.chunk.palette;
|
||||||
|
|
||||||
|
// import net.gensokyoreimagined.nitori.common.world.chunk.LithiumHashPalette;
|
||||||
|
// import net.minecraft.core.IdMap;
|
||||||
|
// import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
|
// import org.jetbrains.annotations.NotNull;
|
||||||
|
// import org.spongepowered.asm.mixin.*;
|
||||||
|
// import net.minecraft.world.level.chunk.Palette;
|
||||||
|
//
|
||||||
|
// import static net.minecraft.world.level.chunk.PalettedContainer.Strategy.LINEAR_PALETTE_FACTORY;
|
||||||
|
// import static net.minecraft.world.level.chunk.PalettedContainer.Strategy.SINGLE_VALUE_PALETTE_FACTORY;
|
||||||
|
//
|
||||||
|
// @Mixin(PalettedContainer.Strategy.class)
|
||||||
|
// public abstract class PalettedContainerMixin {
|
||||||
|
// @Mutable
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// public static PalettedContainer.Strategy SECTION_STATES;
|
||||||
|
//
|
||||||
|
// @Unique
|
||||||
|
// private static final PalettedContainerConfigurationMixin<?>[] BLOCKSTATE_DATA_PROVIDERS;
|
||||||
|
// @Unique
|
||||||
|
// private static final PalettedContainerConfigurationMixin<?>[] BIOME_DATA_PROVIDERS;
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// @Unique
|
||||||
|
// private static final Palette.Factory HASH = LithiumHashPalette::create;
|
||||||
|
// @Mutable
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// public static PalettedContainer.Strategy SECTION_BIOMES;
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// static Palette.Factory GLOBAL_PALETTE_FACTORY;
|
||||||
|
//
|
||||||
|
// /*
|
||||||
|
// * @reason Replace the hash palette from vanilla with our own and change the threshold for usage to only 3 bits,
|
||||||
|
// * as our implementation performs better at smaller key ranges.
|
||||||
|
// * @author JellySquid, 2No2Name (avoid Configuration duplication, use hash palette for 3 bit biomes)
|
||||||
|
// */
|
||||||
|
// static {
|
||||||
|
// Palette.Factory idListFactory = GLOBAL_PALETTE_FACTORY;
|
||||||
|
//
|
||||||
|
// PalettedContainerConfigurationMixin<?> arrayConfiguration4bit = PalettedContainerConfigurationMixin.create(LINEAR_PALETTE_FACTORY, 4);
|
||||||
|
// PalettedContainerConfigurationMixin<?> hashConfiguration4bit = PalettedContainerConfigurationMixin.create(HASH, 4);
|
||||||
|
// BLOCKSTATE_DATA_PROVIDERS = new PalettedContainerConfigurationMixin<?>[]{
|
||||||
|
// PalettedContainerConfigurationMixin.create(SINGLE_VALUE_PALETTE_FACTORY, 0),
|
||||||
|
// // Bits 1-4 must all pass 4 bits as parameter, otherwise chunk sections will corrupt.
|
||||||
|
// arrayConfiguration4bit,
|
||||||
|
// arrayConfiguration4bit,
|
||||||
|
// hashConfiguration4bit,
|
||||||
|
// hashConfiguration4bit,
|
||||||
|
// PalettedContainerConfigurationMixin.create(HASH, 5),
|
||||||
|
// PalettedContainerConfigurationMixin.create(HASH, 6),
|
||||||
|
// PalettedContainerConfigurationMixin.create(HASH, 7),
|
||||||
|
// PalettedContainerConfigurationMixin.create(HASH, 8)
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// SECTION_STATES = new PalettedContainer.Strategy(4) {
|
||||||
|
// @Override
|
||||||
|
// public <A> @NotNull PalettedContainerConfigurationMixin<A> getConfiguration(@NotNull IdMap<A> idList, int bits) {
|
||||||
|
// if (bits >= 0 && bits < BLOCKSTATE_DATA_PROVIDERS.length) {
|
||||||
|
// //noinspection unchecked
|
||||||
|
// return (PalettedContainerConfigurationMixin<A>) BLOCKSTATE_DATA_PROVIDERS[bits];
|
||||||
|
// }
|
||||||
|
// return PalettedContainerConfigurationMixin.create(idListFactory, MathHelper.ceilLog2(idList.size()));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// BIOME_DATA_PROVIDERS = new PalettedContainerConfigurationMixin<?>[]{
|
||||||
|
// PalettedContainerConfigurationMixin.create(SINGLE_VALUE_PALETTE_FACTORY, 0),
|
||||||
|
// PalettedContainerConfigurationMixin.create(LINEAR_PALETTE_FACTORY, 1),
|
||||||
|
// PalettedContainerConfigurationMixin.create(LINEAR_PALETTE_FACTORY, 2),
|
||||||
|
// PalettedContainerConfigurationMixin.create(HASH, 3)
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// SECTION_BIOMES = new PalettedContainer.Strategy(2) {
|
||||||
|
// @Override
|
||||||
|
// public <A> @NotNull PalettedContainerConfigurationMixin<A> getConfiguration(@NotNull IdMap<A> idList, int bits) {
|
||||||
|
// if (bits >= 0 && bits < BIOME_DATA_PROVIDERS.length) {
|
||||||
|
// //noinspection unchecked
|
||||||
|
// return (PalettedContainerConfigurationMixin<A>) BIOME_DATA_PROVIDERS[bits];
|
||||||
|
// }
|
||||||
|
// return PalettedContainerConfigurationMixin.create(idListFactory, MathHelper.ceilLog2(idList.size()));
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package net.gensokyoreimagined.nitori.mixin.world.inline_height;
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.inline_height;
|
||||||
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.world.level.LevelHeightAccessor;
|
import net.minecraft.world.level.LevelHeightAccessor;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package net.gensokyoreimagined.nitori.mixin.world.inline_height;
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.inline_height;
|
||||||
|
|
||||||
//import net.minecraft.core.RegistryAccess;
|
//import net.minecraft.core.RegistryAccess;
|
||||||
//import net.minecraft.resources.ResourceKey;
|
//import net.minecraft.resources.ResourceKey;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.living_entity.enum_values;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.util.EquipmentSlotConstants;
|
||||||
|
import net.minecraft.world.entity.EquipmentSlot;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
@Mixin(LivingEntity.class)
|
||||||
|
public class LivingEntityMixin {
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "collectEquipmentChanges",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/entity/EquipmentSlot;values()[Lnet/minecraft/world/entity/EquipmentSlot;"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private EquipmentSlot[] removeAllocation() {
|
||||||
|
return EquipmentSlotConstants.ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.needs_testing.util.block_tracking;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.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.*;
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
public PalettedContainer<BlockState> states;
|
||||||
|
|
||||||
|
@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.states.maybeHas(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 = "recalcBlockCounts()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);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
@Shadow
|
||||||
|
short nonEmptyBlockCount;
|
||||||
|
@Shadow
|
||||||
|
private short tickingBlockCount;
|
||||||
|
@Shadow
|
||||||
|
private short tickingFluidCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author DoggySazHi
|
||||||
|
* @reason Replace the Paper implementation with Mojang + Lithium implementation
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public void recalcBlockCounts() {
|
||||||
|
this.countsByFlag = new short[BlockStateFlags.NUM_TRACKED_FLAGS];
|
||||||
|
|
||||||
|
BlockStateCounter lv = new BlockStateCounter();
|
||||||
|
this.states.count((state, count) -> {
|
||||||
|
lv.accept(state, count);
|
||||||
|
addToFlagCount(this.countsByFlag, state, (short) count);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.nonEmptyBlockCount = (short)lv.nonEmptyBlockCount;
|
||||||
|
this.tickingBlockCount = (short)lv.randomTickableBlockCount;
|
||||||
|
this.tickingFluidCount = (short)lv.nonEmptyFluidCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "read",
|
||||||
|
at = @At(value = "HEAD")
|
||||||
|
)
|
||||||
|
private void resetData(FriendlyByteBuf buf, CallbackInfo ci) {
|
||||||
|
this.countsByFlag = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "setBlockState(IIILnet/minecraft/world/level/block/state/BlockState;Z)Lnet/minecraft/world/level/block/state/BlockState;",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/level/block/state/BlockState;getFluidState()Lnet/minecraft/world/level/material/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(SectionPos sectionPos) {
|
||||||
|
if (this.listeningMask != 0) {
|
||||||
|
this.changeListener.onChunkSectionInvalidated(sectionPos);
|
||||||
|
this.listeningMask = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.network.microopt;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
|
import io.netty.handler.codec.EncoderException;
|
||||||
|
import net.minecraft.network.Utf8String;
|
||||||
|
import net.minecraft.network.VarInt;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Mixin(Utf8String.class)
|
||||||
|
public class StringEncodingMixin {
|
||||||
|
/**
|
||||||
|
* @author Andrew Steinborn
|
||||||
|
* @reason optimized version
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static void write(ByteBuf buf, CharSequence string, int length) {
|
||||||
|
// Mojang almost gets it right, but stumbles at the finish line...
|
||||||
|
if (string.length() > length) {
|
||||||
|
throw new EncoderException("String too big (was " + string.length() + " characters, max " + length + ")");
|
||||||
|
}
|
||||||
|
int utf8Bytes = ByteBufUtil.utf8Bytes(string);
|
||||||
|
int maxBytesPermitted = ByteBufUtil.utf8MaxBytes(length);
|
||||||
|
if (utf8Bytes > maxBytesPermitted) {
|
||||||
|
throw new EncoderException("String too big (was " + utf8Bytes + " bytes encoded, max " + maxBytesPermitted + ")");
|
||||||
|
} else {
|
||||||
|
VarInt.write(buf, utf8Bytes);
|
||||||
|
buf.writeCharSequence(string, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.network.microopt;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import net.gensokyoreimagined.nitori.mixin.util.network.VarIntUtil;
|
||||||
|
import net.minecraft.network.VarInt;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
@Mixin(VarInt.class)
|
||||||
|
public class VarIntsMixin {
|
||||||
|
/**
|
||||||
|
* @author Andrew Steinborn
|
||||||
|
* @reason optimized version
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static int getByteSize(int v) {
|
||||||
|
return VarIntUtil.getVarIntLength(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Andrew Steinborn
|
||||||
|
* @reason optimized version
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public static ByteBuf write(ByteBuf buf, int i) {
|
||||||
|
// Peel the one and two byte count cases explicitly as they are the most common VarInt sizes
|
||||||
|
// that the server will send, to improve inlining.
|
||||||
|
if ((i & (0xFFFFFFFF << 7)) == 0) {
|
||||||
|
buf.writeByte(i);
|
||||||
|
} else if ((i & (0xFFFFFFFF << 14)) == 0) {
|
||||||
|
int w = (i & 0x7F | 0x80) << 8 | (i >>> 7);
|
||||||
|
buf.writeShort(w);
|
||||||
|
} else {
|
||||||
|
writeOld(buf, i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeOld(ByteBuf buf, int value) {
|
||||||
|
// See https://steinborn.me/posts/performance/how-fast-can-you-write-a-varint/
|
||||||
|
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||||
|
buf.writeByte(value);
|
||||||
|
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||||
|
int w = (value & 0x7F | 0x80) << 8 | (value >>> 7);
|
||||||
|
buf.writeShort(w);
|
||||||
|
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
|
||||||
|
int w = (value & 0x7F | 0x80) << 16 | ((value >>> 7) & 0x7F | 0x80) << 8 | (value >>> 14);
|
||||||
|
buf.writeMedium(w);
|
||||||
|
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
|
||||||
|
int w = (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
|
||||||
|
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21);
|
||||||
|
buf.writeInt(w);
|
||||||
|
} else {
|
||||||
|
int w = (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||||
|
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80);
|
||||||
|
buf.writeInt(w);
|
||||||
|
buf.writeByte(value >>> 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.redirector;
|
||||||
|
|
||||||
|
// https://github.com/MCTeamPotato/Redirector/issues/9
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||||
|
//import org.jetbrains.annotations.Contract;
|
||||||
|
//import org.jetbrains.annotations.NotNull;
|
||||||
|
//import org.objectweb.asm.ClassReader;
|
||||||
|
//import org.objectweb.asm.ClassWriter;
|
||||||
|
//import org.objectweb.asm.Opcodes;
|
||||||
|
//import org.objectweb.asm.tree.AbstractInsnNode;
|
||||||
|
//import org.objectweb.asm.tree.ClassNode;
|
||||||
|
//import org.objectweb.asm.tree.InsnList;
|
||||||
|
//import org.objectweb.asm.tree.MethodNode;
|
||||||
|
//
|
||||||
|
//import java.lang.instrument.ClassFileTransformer;
|
||||||
|
//import java.security.ProtectionDomain;
|
||||||
|
//import java.util.ListIterator;
|
||||||
|
//import java.util.Map;
|
||||||
|
//
|
||||||
|
//public class RedirectorTransformer implements ClassFileTransformer {
|
||||||
|
// public static @NotNull String getSuperClass(byte[] clazz) {
|
||||||
|
// Map<Integer, Integer> utfMap = new Int2IntOpenHashMap();
|
||||||
|
// Map<Integer, Integer> classMap = new Int2IntOpenHashMap();
|
||||||
|
// int constantsCount = readUnsignedShort(clazz, 8);
|
||||||
|
// int passcount = 10;
|
||||||
|
// for (int i = 1; i < constantsCount; i++) {
|
||||||
|
// int size;
|
||||||
|
// switch (clazz[passcount]) {
|
||||||
|
// case 9:
|
||||||
|
// case 10:
|
||||||
|
// case 11:
|
||||||
|
// case 3:
|
||||||
|
// case 4:
|
||||||
|
// case 12:
|
||||||
|
// case 18:
|
||||||
|
// size = 5;
|
||||||
|
// break;
|
||||||
|
// case 5:
|
||||||
|
// case 6:
|
||||||
|
// size = 9;
|
||||||
|
// break;
|
||||||
|
// case 1://UTF8
|
||||||
|
// int UTFSize = readUnsignedShort(clazz, passcount + 1);
|
||||||
|
// size = 3 + UTFSize;
|
||||||
|
// utfMap.put(i, passcount);
|
||||||
|
// break;
|
||||||
|
// case 15:
|
||||||
|
// size = 4;
|
||||||
|
// break;
|
||||||
|
// case 7://class
|
||||||
|
// size = 3;
|
||||||
|
// int index = readUnsignedShort(clazz, passcount + 1);
|
||||||
|
// classMap.put(i, index);
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// size = 3;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// passcount += size;
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// passcount += 4;
|
||||||
|
// passcount = readUnsignedShort(clazz, passcount);
|
||||||
|
// passcount = classMap.get(passcount);
|
||||||
|
// passcount = utfMap.get(passcount);
|
||||||
|
// int UTFSize = readUnsignedShort(clazz, passcount + 1);
|
||||||
|
// return readUTF8(clazz, passcount + 3, UTFSize);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Contract(pure = true)
|
||||||
|
// public static int readUnsignedShort(byte @NotNull [] b, int index) {
|
||||||
|
// return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Contract(value = "_, _, _ -> new", pure = true)
|
||||||
|
// public static @NotNull String readUTF8(byte[] b, int index, int length) {
|
||||||
|
// char[] str = new char[length];
|
||||||
|
// for (int i = 0; i < length; i++) {
|
||||||
|
// str[i] = (char) b[i + index];
|
||||||
|
// }
|
||||||
|
// return new String(str);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] basicClass) {
|
||||||
|
// try {
|
||||||
|
// String superClass = getSuperClass(basicClass);
|
||||||
|
// if (!"java/lang/Enum".equals(superClass)) return basicClass;
|
||||||
|
// ClassReader classReader = new ClassReader(basicClass);
|
||||||
|
// if ("java/lang/Enum".equals(classReader.getSuperName())) {
|
||||||
|
// ClassNode cn = new ClassNode();
|
||||||
|
// classReader.accept(cn, 0);
|
||||||
|
// for (MethodNode mn : cn.methods) {
|
||||||
|
// if ("values".equals(mn.name) && mn.desc.contains("()")) {
|
||||||
|
// InsnList il = mn.instructions;
|
||||||
|
// ListIterator<AbstractInsnNode> iterator = il.iterator();
|
||||||
|
// AbstractInsnNode n1 = null;
|
||||||
|
// AbstractInsnNode n2 = null;
|
||||||
|
// while (iterator.hasNext()) {
|
||||||
|
// AbstractInsnNode note = iterator.next();
|
||||||
|
// if (Opcodes.GETSTATIC == note.getOpcode()) {
|
||||||
|
// n1 = note;
|
||||||
|
// } else if (Opcodes.ARETURN == note.getOpcode()) {
|
||||||
|
// n2 = note;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// il.clear();
|
||||||
|
// il.add(n1);
|
||||||
|
// il.add(n2);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
|
||||||
|
// cn.accept(classWriter);
|
||||||
|
// return classWriter.toByteArray();
|
||||||
|
// }
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// return basicClass;
|
||||||
|
// }
|
||||||
|
// return basicClass;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -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,33 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.shapes.precompute_shape_arrays;
|
||||||
|
|
||||||
|
import net.minecraft.world.phys.shapes.CubePointRange;
|
||||||
|
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 org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(CubePointRange.class)
|
||||||
|
public class FractionalDoubleListMixin {
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private int parts;
|
||||||
|
|
||||||
|
private double scale;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(I)V", at = @At("RETURN"))
|
||||||
|
public void initScale(int sectionCount, CallbackInfo ci) {
|
||||||
|
this.scale = 1.0D / this.parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JellySquid
|
||||||
|
* @reason Replace division with multiplication
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public double getDouble(int position) {
|
||||||
|
return position * this.scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.shapes.precompute_shape_arrays;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.doubles.DoubleList;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.phys.shapes.CubePointRange;
|
||||||
|
import net.minecraft.world.phys.shapes.CubeVoxelShape;
|
||||||
|
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
|
@Mixin(CubeVoxelShape.class)
|
||||||
|
public class SimpleVoxelShapeMixin {
|
||||||
|
private static final Direction.Axis[] AXIS = Direction.Axis.values();
|
||||||
|
|
||||||
|
private DoubleList[] list;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private static Constructor<CubePointRange> nitori$cubePointRangeConstructor;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private CubePointRange nitori$cubePointRange(int sectionCount) {
|
||||||
|
try {
|
||||||
|
if (nitori$cubePointRangeConstructor == null) {
|
||||||
|
nitori$cubePointRangeConstructor = CubePointRange.class.getDeclaredConstructor(int.class);
|
||||||
|
nitori$cubePointRangeConstructor.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nitori$cubePointRangeConstructor.newInstance(sectionCount);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AssertionError("Failed to find CubePointRange constructor - " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "<init>", at = @At("RETURN"))
|
||||||
|
private void onConstructed(DiscreteVoxelShape voxels, CallbackInfo ci) {
|
||||||
|
this.list = new DoubleList[AXIS.length];
|
||||||
|
|
||||||
|
for (Direction.Axis axis : AXIS) {
|
||||||
|
this.list[axis.ordinal()] = nitori$cubePointRange(voxels.getSize(axis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author JellySquid
|
||||||
|
* @reason Use the cached array
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public DoubleList getCoords(Direction.Axis axis) {
|
||||||
|
return this.list[axis.ordinal()];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.shapes.specialized_shapes;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.doubles.DoubleList;
|
||||||
|
import net.minecraft.core.AxisCycle;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement faster methods for determining penetration during collision resolution.
|
||||||
|
*/
|
||||||
|
@Mixin(VoxelShape.class)
|
||||||
|
public abstract class VoxelShapeMixin {
|
||||||
|
private static final double POSITIVE_EPSILON = +1.0E-7D;
|
||||||
|
private static final double NEGATIVE_EPSILON = -1.0E-7D;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
public DiscreteVoxelShape shape;
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract boolean isEmpty();
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
protected abstract double get(Direction.Axis axis, int index);
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
public abstract DoubleList getCoords(Direction.Axis axis);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @reason Use optimized implementation which delays searching for coordinates as long as possible
|
||||||
|
* @author JellySquid
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public double collideX(AxisCycle cycleDirection, AABB box, double maxDist) {
|
||||||
|
if (this.isEmpty()) {
|
||||||
|
return maxDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Math.abs(maxDist) < POSITIVE_EPSILON) {
|
||||||
|
return 0.0D;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxisCycle cycle = cycleDirection.inverse();
|
||||||
|
|
||||||
|
Direction.Axis axisX = cycle.cycle(Direction.Axis.X);
|
||||||
|
Direction.Axis axisY = cycle.cycle(Direction.Axis.Y);
|
||||||
|
Direction.Axis axisZ = cycle.cycle(Direction.Axis.Z);
|
||||||
|
|
||||||
|
int minY = Integer.MIN_VALUE;
|
||||||
|
int maxY = Integer.MIN_VALUE;
|
||||||
|
int minZ = Integer.MIN_VALUE;
|
||||||
|
int maxZ = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
int x, y, z;
|
||||||
|
|
||||||
|
double dist;
|
||||||
|
|
||||||
|
if (maxDist > 0.0D) {
|
||||||
|
double max = box.max(axisX);
|
||||||
|
int maxIdx = this.findIndex(axisX, max - POSITIVE_EPSILON);
|
||||||
|
|
||||||
|
int maxX = this.shape.getSize(axisX);
|
||||||
|
|
||||||
|
for (x = maxIdx + 1; x < maxX; ++x) {
|
||||||
|
minY = minY == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisY, box.min(axisY) + POSITIVE_EPSILON)) : minY;
|
||||||
|
maxY = maxY == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisY), this.findIndex(axisY, box.max(axisY) - POSITIVE_EPSILON) + 1) : maxY;
|
||||||
|
|
||||||
|
for (y = minY; y < maxY; ++y) {
|
||||||
|
minZ = minZ == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisZ, box.min(axisZ) + POSITIVE_EPSILON)) : minZ;
|
||||||
|
maxZ = maxZ == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisZ), this.findIndex(axisZ, box.max(axisZ) - POSITIVE_EPSILON) + 1) : maxZ;
|
||||||
|
|
||||||
|
for (z = minZ; z < maxZ; ++z) {
|
||||||
|
if (this.shape.isFullWide(cycle, x, y, z)) {
|
||||||
|
dist = this.get(axisX, x) - max;
|
||||||
|
|
||||||
|
if (dist >= NEGATIVE_EPSILON) {
|
||||||
|
maxDist = Math.min(maxDist, dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (maxDist < 0.0D) {
|
||||||
|
double min = box.min(axisX);
|
||||||
|
int minIdx = this.findIndex(axisX, min + POSITIVE_EPSILON);
|
||||||
|
|
||||||
|
for (x = minIdx - 1; x >= 0; --x) {
|
||||||
|
minY = minY == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisY, box.min(axisY) + POSITIVE_EPSILON)) : minY;
|
||||||
|
maxY = maxY == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisY), this.findIndex(axisY, box.max(axisY) - POSITIVE_EPSILON) + 1) : maxY;
|
||||||
|
|
||||||
|
for (y = minY; y < maxY; ++y) {
|
||||||
|
minZ = minZ == Integer.MIN_VALUE ? Math.max(0, this.findIndex(axisZ, box.min(axisZ) + POSITIVE_EPSILON)) : minZ;
|
||||||
|
maxZ = maxZ == Integer.MIN_VALUE ? Math.min(this.shape.getSize(axisZ), this.findIndex(axisZ, box.max(axisZ) - POSITIVE_EPSILON) + 1) : maxZ;
|
||||||
|
|
||||||
|
for (z = minZ; z < maxZ; ++z) {
|
||||||
|
if (this.shape.isFullWide(cycle, x, y, z)) {
|
||||||
|
dist = this.get(axisX, x + 1) - min;
|
||||||
|
|
||||||
|
if (dist <= POSITIVE_EPSILON) {
|
||||||
|
maxDist = Math.max(maxDist, dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxDist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inlines the lambda passed to MathHelper#binarySearch. Simplifies the implementation very slightly for additional
|
||||||
|
* speed.
|
||||||
|
*
|
||||||
|
* @reason Use faster implementation
|
||||||
|
* @author JellySquid
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public int findIndex(Direction.Axis axis, double coord) {
|
||||||
|
DoubleList list = this.getCoords(axis);
|
||||||
|
|
||||||
|
int size = this.shape.getSize(axis);
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int end = size + 1 - start;
|
||||||
|
|
||||||
|
while (end > 0) {
|
||||||
|
int middle = end / 2;
|
||||||
|
int idx = start + middle;
|
||||||
|
|
||||||
|
if (idx >= 0 && (idx > size || coord < list.getDouble(idx))) {
|
||||||
|
end = middle;
|
||||||
|
} else {
|
||||||
|
start = idx + 1;
|
||||||
|
end -= middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return start - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.shapes.specialized_shapes;
|
||||||
|
|
||||||
|
//TODO: Later
|
||||||
|
|
||||||
|
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeAlignedCuboid;
|
||||||
|
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeEmpty;
|
||||||
|
//import me.jellysquid.mods.lithium.common.shapes.VoxelShapeSimpleCube;
|
||||||
|
//import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
|
||||||
|
//import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
|
||||||
|
//import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
//import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
//import org.spongepowered.asm.mixin.*;
|
||||||
|
//
|
||||||
|
///**
|
||||||
|
// * Shape specialization allows us to optimize comparison logic by guaranteeing certain constraints about the
|
||||||
|
// * configuration of vertices in a given shape. For example, most block shapes consist of only one cuboid and by
|
||||||
|
// * nature, only one voxel. This fact can be taken advantage of to create an optimized implementation which avoids
|
||||||
|
// * scanning over voxels as there are only ever two given vertices in the shape, allowing simple math operations to be
|
||||||
|
// * used for determining intersection and penetration.
|
||||||
|
// * <p>
|
||||||
|
// * In most cases, comparison logic is rather simple as the game often only deals with empty shapes or simple cubes.
|
||||||
|
// * Specialization provides a significant speed-up to entity collision resolution and various other parts of the game
|
||||||
|
// * without needing invasive patches, as we can simply replace the types returned by this class. Modern processors
|
||||||
|
// * (along with the help of the potent JVM) make the cost of dynamic dispatch negligible when compared to the execution
|
||||||
|
// * times of shape comparison methods.
|
||||||
|
// */
|
||||||
|
//@Mixin(Shapes.class)
|
||||||
|
//public abstract class VoxelShapesMixin {
|
||||||
|
// @Mutable
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// public static final VoxelShape INFINITY;
|
||||||
|
//
|
||||||
|
// @Mutable
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// private static final VoxelShape BLOCK;
|
||||||
|
//
|
||||||
|
// @Mutable
|
||||||
|
// @Shadow
|
||||||
|
// @Final
|
||||||
|
// private static final VoxelShape EMPTY;
|
||||||
|
//
|
||||||
|
// private static final DiscreteVoxelShape FULL_CUBE_VOXELS;
|
||||||
|
//
|
||||||
|
// // Re-initialize the global cached shapes with our specialized ones. This will happen right after all the static
|
||||||
|
// // state has been initialized and before any external classes access it.
|
||||||
|
// static {
|
||||||
|
// // [VanillaCopy] The FULL_CUBE and UNBOUNDED shape is initialized with a single 1x1x1 voxel as neither will
|
||||||
|
// // contain multiple inner cuboids.
|
||||||
|
// FULL_CUBE_VOXELS = new BitSetDiscreteVoxelShape(1, 1, 1);
|
||||||
|
// FULL_CUBE_VOXELS.fill(0, 0, 0);
|
||||||
|
//
|
||||||
|
// // Used in some rare cases to indicate a shape which encompasses the entire world (such as a moving world border)
|
||||||
|
// INFINITY = new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY,
|
||||||
|
// Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
|
||||||
|
//
|
||||||
|
// // Represents a full-block cube shape, such as that for a dirt block.
|
||||||
|
// BLOCK = new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
|
||||||
|
//
|
||||||
|
// // Represents an empty cube shape with no vertices that cannot be collided with.
|
||||||
|
// EMPTY = new VoxelShapeEmpty(new BitSetDiscreteVoxelShape(0, 0, 0));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Vanilla implements some very complex logic in this function in order to allow entity boxes to be used in
|
||||||
|
// * collision resolution the same way as block shapes. The specialized simple cube shape however can trivially
|
||||||
|
// * represent these cases with nothing more than the two vertexes. This provides a modest speed up for entity
|
||||||
|
// * collision code by allowing them to also use our optimized shapes.
|
||||||
|
// * <p>
|
||||||
|
// * Vanilla uses different kinds of VoxelShapes depending on the size and position of the box.
|
||||||
|
// * A box that isn't aligned with 1/8th of a block will become a very simple ArrayVoxelShape, while others
|
||||||
|
// * will become a "SimpleVoxelShape" with a BitSetVoxelSet that possibly has a higher resolution (1-3 bits) per axis.
|
||||||
|
// * <p>
|
||||||
|
// * Shapes that have a high resolution (e.g. extended piston base has 2 bits on one axis) have collision
|
||||||
|
// * layers inside them. An upwards extended piston base has extra collision boxes at 0.25 and 0.5 height.
|
||||||
|
// * Slabs don't have extra collision boxes, because they are only as high as the smallest height that is possible
|
||||||
|
// * with their bit resolution (1, so half a block).
|
||||||
|
// *
|
||||||
|
// * @reason Use our optimized shape types
|
||||||
|
// * @author JellySquid, 2No2Name
|
||||||
|
// */
|
||||||
|
// @Overwrite
|
||||||
|
// public static VoxelShape cuboidUnchecked(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
|
||||||
|
// if (maxX - minX < 1.0E-7D || maxY - minY < 1.0E-7D || maxZ - minZ < 1.0E-7D) {
|
||||||
|
// return EMPTY;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// int xRes;
|
||||||
|
// int yRes;
|
||||||
|
// int zRes;
|
||||||
|
// //findRequiredBitResolution(...) looks unnecessarily slow, and it seems to unintentionally return -1 on inputs like -1e-8,
|
||||||
|
// //A faster implementation is not in the scope of this mixin.
|
||||||
|
//
|
||||||
|
// //Description of what vanilla does:
|
||||||
|
// //If the VoxelShape cannot be represented by a BitSet with 3 bit resolution on any axis (BitSetVoxelSet),
|
||||||
|
// //a shape without boxes inside will be used in vanilla (ArrayVoxelShape with only 2 PointPositions on each axis)
|
||||||
|
//
|
||||||
|
// if ((xRes = Shapes.findBits(minX, maxX)) < 0 ||
|
||||||
|
// (yRes = Shapes.findBits(minY, maxY)) < 0 ||
|
||||||
|
// (zRes = Shapes.findBits(minZ, maxZ)) < 0) {
|
||||||
|
// //vanilla uses ArrayVoxelShape here without any rounding of the coordinates
|
||||||
|
// return new VoxelShapeSimpleCube(FULL_CUBE_VOXELS, minX, minY, minZ, maxX, maxY, maxZ);
|
||||||
|
// } else {
|
||||||
|
// if (xRes == 0 && yRes == 0 && zRes == 0) {
|
||||||
|
// return BLOCK;
|
||||||
|
// }
|
||||||
|
// // vanilla would use a SimpleVoxelShape with a BitSetVoxelSet of resolution of xRes, yRes, zRes here, we match its behavior
|
||||||
|
// return new VoxelShapeAlignedCuboid(Math.round(minX * 8D) / 8D, Math.round(minY * 8D) / 8D, Math.round(minZ * 8D) / 8D,
|
||||||
|
// Math.round(maxX * 8D) / 8D, Math.round(maxY * 8D) / 8D, Math.round(maxZ * 8D) / 8D, xRes, yRes, zRes);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.accessors;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||||
|
import net.minecraft.world.level.entity.TransientEntitySectionManager;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(TransientEntitySectionManager.class)
|
||||||
|
public interface ClientEntityManagerAccessor<T extends EntityAccess> {
|
||||||
|
@Accessor
|
||||||
|
EntitySectionStorage<T> getSectionStorage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.accessors;
|
||||||
|
|
||||||
|
import net.minecraft.util.ClassInstanceMultiMap;
|
||||||
|
import net.minecraft.world.level.entity.EntitySection;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(EntitySection.class)
|
||||||
|
public interface EntityTrackingSectionAccessor<T> {
|
||||||
|
@Accessor
|
||||||
|
ClassInstanceMultiMap<T> getStorage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.accessors;
|
||||||
|
|
||||||
|
import net.minecraft.world.level.entity.EntityAccess;
|
||||||
|
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||||
|
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
|
||||||
|
@Mixin(PersistentEntitySectionManager.class)
|
||||||
|
public interface ServerEntityManagerAccessor<T extends EntityAccess> {
|
||||||
|
@Accessor
|
||||||
|
EntitySectionStorage<T> getSectionStorage();
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.accessors;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.level.entity.PersistentEntitySectionManager;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
|
||||||
|
// Does not work on Paper...
|
||||||
|
@Mixin(ServerLevel.class)
|
||||||
|
public interface ServerWorldAccessor {
|
||||||
|
@Accessor
|
||||||
|
PersistentEntitySectionManager<Entity> getEntityManager();
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.block_tracking;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.block.BlockStateFlagHolder;
|
||||||
|
import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
|
||||||
|
import net.gensokyoreimagined.nitori.common.block.TrackedBlockStatePredicate;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
|
||||||
|
@Mixin(BlockBehaviour.BlockStateBase.class)
|
||||||
|
public class AbstractBlockStateMixin implements BlockStateFlagHolder {
|
||||||
|
@Unique
|
||||||
|
private int flags;
|
||||||
|
|
||||||
|
@Inject(method = "initCache", at = @At("RETURN"))
|
||||||
|
private void init(CallbackInfo ci) {
|
||||||
|
this.initFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void initFlags() {
|
||||||
|
TrackedBlockStatePredicate.FULLY_INITIALIZED.set(true);
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < BlockStateFlags.FLAGS.length; i++) {
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (BlockStateFlags.FLAGS[i].test((BlockState) (Object) this)) {
|
||||||
|
flags |= 1 << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int lithium$getAllFlags() {
|
||||||
|
return this.flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.util.network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps VarInt byte sizes to a lookup table corresponding to the number of bits in the integer,
|
||||||
|
* from zero to 32.
|
||||||
|
*/
|
||||||
|
public class VarIntUtil {
|
||||||
|
private static final int[] VARINT_EXACT_BYTE_LENGTHS = new int[33];
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (int i = 0; i <= 32; ++i) {
|
||||||
|
VARINT_EXACT_BYTE_LENGTHS[i] = (int) Math.ceil((31d - (i - 1)) / 7d);
|
||||||
|
}
|
||||||
|
VARINT_EXACT_BYTE_LENGTHS[32] = 1; // Special case for 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getVarIntLength(int value) {
|
||||||
|
return VARINT_EXACT_BYTE_LENGTHS[Integer.numberOfLeadingZeros(value)];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.vmp.entity.move_zero_velocity;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.MoverType;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(Entity.class)
|
||||||
|
public class MixinEntity {
|
||||||
|
|
||||||
|
@Shadow private AABB bb;
|
||||||
|
@Unique
|
||||||
|
private boolean boundingBoxChanged = false;
|
||||||
|
|
||||||
|
@Inject(method = "move", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void onMove(MoverType movementType, Vec3 movement, CallbackInfo ci) {
|
||||||
|
if (!boundingBoxChanged && movement.equals(Vec3.ZERO)) {
|
||||||
|
ci.cancel();
|
||||||
|
boundingBoxChanged = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "setBoundingBox", at = @At("HEAD"))
|
||||||
|
private void onBoundingBoxChanged(AABB boundingBox, CallbackInfo ci) {
|
||||||
|
if (!this.bb.equals(boundingBox)) boundingBoxChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.vmp.general.collections;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||||
|
import net.gensokyoreimagined.nitori.access.ITypeFilterableList;
|
||||||
|
import net.minecraft.util.ClassInstanceMultiMap;
|
||||||
|
import org.objectweb.asm.Opcodes;
|
||||||
|
import org.spongepowered.asm.mixin.*;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Mixin(value = ClassInstanceMultiMap.class, priority = 1005) // priority compatibility hack for lithium
|
||||||
|
public abstract class MixinTypeFilterableList<T> extends AbstractCollection<T> implements ITypeFilterableList {
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow @Final private Map<Class<?>, List<T>> byClass;
|
||||||
|
|
||||||
|
@Mutable
|
||||||
|
@Shadow @Final private List<T> allInstances;
|
||||||
|
|
||||||
|
@Shadow @Final private Class<T> baseClass;
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/util/ClassInstanceMultiMap;byClass:Ljava/util/Map;", opcode = Opcodes.PUTFIELD))
|
||||||
|
private void redirectSetElementsByType(ClassInstanceMultiMap<T> instance, Map<Class<?>, List<T>> value) {
|
||||||
|
this.byClass = new Object2ObjectLinkedOpenHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "FIELD", target = "Lnet/minecraft/util/ClassInstanceMultiMap;allInstances:Ljava/util/List;", opcode = Opcodes.PUTFIELD))
|
||||||
|
private void redirectSetAllElements(ClassInstanceMultiMap<T> instance, List<T> value) {
|
||||||
|
this.allInstances = new ObjectArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false))
|
||||||
|
private HashMap<?, ?> redirectNewHashMap() {
|
||||||
|
return null; // avoid unnecessary alloc
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Lists;newArrayList()Ljava/util/ArrayList;", remap = false))
|
||||||
|
private ArrayList<?> redirectNewArrayList() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getBackingArray() {
|
||||||
|
return ((ObjectArrayList<T>) this.allInstances).elements();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ishland
|
||||||
|
* @reason use fastutil array list for faster iteration & use array for filtering iteration
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public <S> Collection<S> find(Class<S> type) {
|
||||||
|
List<T> cached = this.byClass.get(type);
|
||||||
|
if (cached != null) return (Collection<S>) cached;
|
||||||
|
|
||||||
|
if (!this.baseClass.isAssignableFrom(type)) {
|
||||||
|
throw new IllegalArgumentException("Don't know how to search for " + type);
|
||||||
|
} else {
|
||||||
|
List<? extends T> list = this.byClass.computeIfAbsent(type,
|
||||||
|
typeClass -> {
|
||||||
|
ObjectArrayList<T> ts = new ObjectArrayList<>(this.allInstances.size());
|
||||||
|
for (Object _allElement : ((ObjectArrayList<T>) this.allInstances).elements()) {
|
||||||
|
if (typeClass.isInstance(_allElement)) {
|
||||||
|
ts.add((T) _allElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ts;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return (Collection<S>) list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching;
|
||||||
|
|
||||||
|
import net.minecraft.server.level.ChunkTrackingView;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
|
||||||
|
@Mixin(ChunkTrackingView.class)
|
||||||
|
public interface MixinChunkFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ishland
|
||||||
|
* @reason use chebyshev distance
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
static boolean isWithinDistance(int centerX, int centerZ, int viewDistance, int x, int z, boolean includeEdge) {
|
||||||
|
int actualViewDistance = viewDistance + (includeEdge ? 1 : 0);
|
||||||
|
int xDistance = Math.abs(centerX - x);
|
||||||
|
int zDistance = Math.abs(centerZ - z);
|
||||||
|
return xDistance <= actualViewDistance && zDistance <= actualViewDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.chunkwatching.PlayerClientVDTracking;
|
||||||
|
import net.minecraft.server.level.ClientInformation;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ServerPlayer.class)
|
||||||
|
public class MixinServerPlayerEntity implements PlayerClientVDTracking {
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean vdChanged = false;
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private int clientVD = 2;
|
||||||
|
|
||||||
|
@Inject(method = "updateOptions", at = @At("HEAD"))
|
||||||
|
private void onClientSettingsChanged(ClientInformation packet, CallbackInfo ci) {
|
||||||
|
final int currentVD = packet.viewDistance();
|
||||||
|
if (currentVD != this.clientVD) this.vdChanged = true;
|
||||||
|
this.clientVD = Math.max(2, currentVD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "restoreFrom", at = @At("RETURN"))
|
||||||
|
private void onPlayerCopy(ServerPlayer oldPlayer, boolean alive, CallbackInfo ci) {
|
||||||
|
this.clientVD = ((PlayerClientVDTracking) oldPlayer).getClientViewDistance();
|
||||||
|
this.vdChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
@Override
|
||||||
|
public boolean isClientViewDistanceChanged() {
|
||||||
|
return this.vdChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
@Override
|
||||||
|
public int getClientViewDistance() {
|
||||||
|
this.vdChanged = false;
|
||||||
|
return this.clientVD;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.vmp.playerwatching.optimize_nearby_player_lookups;
|
||||||
|
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.LivingEntity;
|
||||||
|
import net.minecraft.world.entity.Mob;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@Mixin(Mob.class)
|
||||||
|
public abstract class MixinMobEntity extends LivingEntity {
|
||||||
|
|
||||||
|
protected MixinMobEntity(EntityType<? extends LivingEntity> entityType, Level world) {
|
||||||
|
super(entityType, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(method = "checkDespawn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;findNearbyPlayer(Lnet/minecraft/world/entity/Entity;DLjava/util/function/Predicate;)Lnet/minecraft/world/entity/player/Player;"))
|
||||||
|
private Player redirectGetClosestPlayer(Level instance, Entity entity, double v, Predicate predicate) {
|
||||||
|
final Player closestPlayer = instance.getNearestPlayer(entity, this.getType().getCategory().getDespawnDistance());
|
||||||
|
if (closestPlayer != null) {
|
||||||
|
return closestPlayer;
|
||||||
|
} else {
|
||||||
|
final List<? extends Player> players = this.level().players();
|
||||||
|
if (players.isEmpty()) return null;
|
||||||
|
return players.get(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.support_cache;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.world.blockentity.SupportCache;
|
||||||
|
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.core.BlockPos;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(BlockEntity.class)
|
||||||
|
public abstract class BlockEntityMixin implements SupportCache {
|
||||||
|
@Shadow
|
||||||
|
public abstract BlockEntityType<?> getType();
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private boolean supportTestResult;
|
||||||
|
|
||||||
|
@Inject(method = "<init>(Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;)V", at = @At("RETURN"))
|
||||||
|
private void initSupportCache(BlockEntityType<?> type, BlockPos pos, BlockState cachedState, CallbackInfo ci) {
|
||||||
|
this.supportTestResult = this.getType().isValid(cachedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "setBlockState", at = @At("RETURN"))
|
||||||
|
private void updateSupportCache(BlockState cachedState, CallbackInfo ci) {
|
||||||
|
this.supportTestResult = this.getType().isValid(cachedState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean lithium$isSupported() {
|
||||||
|
return this.supportTestResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package net.gensokyoreimagined.nitori.mixin.world.block_entity_ticking.support_cache;
|
||||||
|
|
||||||
|
import net.gensokyoreimagined.nitori.common.world.blockentity.SupportCache;
|
||||||
|
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.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Slice;
|
||||||
|
|
||||||
|
@Mixin(targets = "net.minecraft.world.level.chunk.LevelChunk$BoundTickingBlockEntity")
|
||||||
|
public class DirectBlockEntityTickInvokerMixin<T extends BlockEntity> {
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
@Final
|
||||||
|
private T blockEntity;
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "tick",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/level/chunk/LevelChunk;getBlockState(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;"
|
||||||
|
),
|
||||||
|
slice = @Slice(
|
||||||
|
from = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/util/function/Supplier;)V"),
|
||||||
|
to = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/entity/BlockEntityTicker;tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BlockEntity;)V")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private BlockState getCachedState(LevelChunk chunk, BlockPos pos) {
|
||||||
|
return this.blockEntity.getBlockState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Redirect(
|
||||||
|
method = "tick()V",
|
||||||
|
at = @At(
|
||||||
|
value = "INVOKE",
|
||||||
|
target = "Lnet/minecraft/world/level/block/entity/BlockEntityType;isValid(Lnet/minecraft/world/level/block/state/BlockState;)Z"
|
||||||
|
),
|
||||||
|
slice = @Slice(
|
||||||
|
from = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/util/function/Supplier;)V"),
|
||||||
|
to = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/entity/BlockEntityTicker;tick(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/entity/BlockEntity;)V")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
private boolean cachedIsSupported(BlockEntityType<?> blockEntityType, BlockState block) {
|
||||||
|
return ((SupportCache) this.blockEntity).lithium$isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user