Compare commits
103 Commits
dev-multit
...
next
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa1632a21f | ||
|
|
5eabd83633 | ||
|
|
dd31ed6b54 | ||
|
|
36e5c571de | ||
|
|
94fc986edd | ||
|
|
53ebfb462c | ||
|
|
c2a7c25ec3 | ||
|
|
3e5edd79e8 | ||
|
|
346acf5e98 | ||
|
|
29715101b3 | ||
|
|
610c5fb21e | ||
|
|
ac69c6222c | ||
|
|
82e8165108 | ||
|
|
808112315d | ||
|
|
4aada290b1 | ||
|
|
c8f42ea2be | ||
|
|
1361c4b05c | ||
|
|
036aaf8b72 | ||
|
|
c244617cfd | ||
|
|
1feb46711e | ||
|
|
fd09c9ae8a | ||
|
|
eea4f8e0d9 | ||
|
|
d4ae8a6336 | ||
|
|
59422498c4 | ||
|
|
566eebfe5a | ||
|
|
663483c5e6 | ||
|
|
26ae51054d | ||
|
|
f539a95579 | ||
|
|
0bf5e28ef3 | ||
|
|
b1cfedae2e | ||
|
|
711f9c08d5 | ||
|
|
604c2fc817 | ||
|
|
eb97d71e9e | ||
|
|
5762d8aabd | ||
|
|
afa4f6f932 | ||
|
|
417f9e6f4b | ||
|
|
a1a0a67283 | ||
|
|
7cad031876 | ||
|
|
e036eb1ca6 | ||
|
|
728ba09022 | ||
|
|
b8dd2ddd4c | ||
|
|
377cdd6a7a | ||
|
|
00f122741b | ||
|
|
c65eb6eecf | ||
|
|
6cbee7408f | ||
|
|
dabb5dd159 | ||
|
|
a98e77aade | ||
|
|
8a772889b5 | ||
|
|
330a563d22 | ||
|
|
eb42627111 | ||
|
|
4d5b6e71b6 | ||
|
|
2a7939026f | ||
|
|
eb47da2772 | ||
|
|
cd9e06fea7 | ||
|
|
4aa707b8b5 | ||
|
|
f598d6a5f0 | ||
|
|
b5f1450bb0 | ||
|
|
f155fec797 | ||
|
|
e9fd0a0363 | ||
|
|
123ffb0988 | ||
|
|
ed6e029363 | ||
|
|
a824c25224 | ||
|
|
5bb7e63d75 | ||
|
|
c1e1e5d27c | ||
|
|
5898896704 | ||
|
|
9fb9753202 | ||
|
|
1e93913b63 | ||
|
|
feaec74c94 | ||
|
|
7e795056a8 | ||
|
|
ce6219c206 | ||
|
|
0dbf375906 | ||
|
|
a0100cd103 | ||
|
|
c51fcad23a | ||
|
|
c3993313b9 | ||
|
|
d191e500f4 | ||
|
|
5a9831e94c | ||
|
|
4651de95e9 | ||
|
|
4ce57781de | ||
|
|
a5fe6b78ab | ||
|
|
b77e7c5cc7 | ||
|
|
a9be79a6a0 | ||
|
|
f5b994768f | ||
|
|
0a0ed20dde | ||
|
|
15703cbcef | ||
|
|
3d69cdcb52 | ||
|
|
c7b4f3aaeb | ||
|
|
4ee7f0621a | ||
|
|
bc1b851a4e | ||
|
|
8314c4b05f | ||
|
|
870c519bc3 | ||
|
|
0a35a86110 | ||
|
|
d985b507e4 | ||
|
|
71fc6f332b | ||
|
|
a0794eaab9 | ||
|
|
e84af35838 | ||
|
|
354362e4e9 | ||
|
|
61648b6990 | ||
|
|
ef7de4b6c3 | ||
|
|
1f87ad2546 | ||
|
|
6183877840 | ||
|
|
f8c45602df | ||
|
|
e2591a635b | ||
|
|
218206b54b |
12
.github/workflows/build-commit.yml
vendored
12
.github/workflows/build-commit.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
- name: Initialize caches
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -31,17 +31,17 @@ jobs:
|
||||
key: ${{ runner.os }}-build-commit-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-commit-
|
||||
- name: Gradle deez
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
- name: Build artifacts
|
||||
run: ./gradlew clean :spotlessApply build
|
||||
run: ./gradlew build
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nitori-artifacts-${{ steps.ref.outputs.branch }}
|
||||
path: build/libs/*.jar
|
||||
- name: Reposilite upload
|
||||
uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
|
||||
with:
|
||||
arguments: publish
|
||||
run: ./gradlew publish
|
||||
env:
|
||||
USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_GENSOREPOUSERNAME }}
|
||||
TOKEN: ${{ secrets.ORG_GRADLE_PROJECT_GENSOREPOPASSWORD }}
|
||||
57
README.md
57
README.md
@@ -1,34 +1,39 @@
|
||||
# Nitori
|
||||
A performance mod that converts patches into mixins using the Ignite Framework for Paper/Spigot.
|
||||
|
||||
## Optimizations
|
||||
## Optimizations:
|
||||
This plugin provides the following optimizations:
|
||||
- [x] Iterate entity trackers faster by using Int2ObjectLinkedOpenHashMap
|
||||
- [ ] Reduce constants allocations
|
||||
- [x] Entity Micro Optimizations
|
||||
- [ ] Lithium mixins
|
||||
- [x] Fast util
|
||||
- [x] HashedReferenceList
|
||||
- [x] CompactSineLUT
|
||||
- [x] Fast retrieval
|
||||
- [x] Cached hashcode
|
||||
- [x] Store gamerules in fastutil hashmap
|
||||
- [ ] Precompute shape arrays
|
||||
- [ ] Collections.attributes
|
||||
- [ ] Collections.entity_by_type
|
||||
- [ ] Collections.entity_filtering
|
||||
- [ ] Chunk serialization
|
||||
- [x] Cache iterate outwards
|
||||
- [ ] Block moving block shapes
|
||||
- [ ] Shapes blockstate cache
|
||||
- [x] Lithium gen
|
||||
- [ ] Ai sensor secondary POI
|
||||
- [ ] World tick scheduler
|
||||
- [x] Smarter statistics-ticking
|
||||
- [ ] Async Pathfinding
|
||||
- [ ] Multithreaded Tracker
|
||||
- Faster Entity tracker by utilizing **Multiple Cores** this will allow larger servers to have way more entities
|
||||
- Async NBT data saving which improves where paper doesn't on world saving
|
||||
- Many of Lithium's Optimization patches which includes:
|
||||
- Faster Math
|
||||
- Faster Entity retrieval
|
||||
- Reduced Memory allocations
|
||||
- Improved inlined logics
|
||||
- Improved collections
|
||||
- Pre-Computed Shape arrays
|
||||
- Improved Block Entity tickings
|
||||
- Lithium Chunk Gen
|
||||
- Mob Ai Improvements (soon)
|
||||
- Fast BlockPos
|
||||
- Faster entity related inmterractions (Hand swing, Sprinting particles etc.)
|
||||
- ...and much more
|
||||
- Includes some of the patches from Very Many Players (VMP)'s
|
||||
- Improved player tracking logic
|
||||
- Improved TypeFilterableList
|
||||
- If entity velocity is zero it won't send it as packets
|
||||
- Improved Player lookups
|
||||
- Faster VarInts
|
||||
- Some patches from Potatoptimize
|
||||
- Way faster math
|
||||
- Faster rotation logic
|
||||
- Many Inlined logics
|
||||
|
||||
**NOTE: This mod may or may not alter the default behaviors of some mob AI.**
|
||||
## Optimizations To-Do:
|
||||
- Async Mob Pathfinding
|
||||
- Multithreading starlight using ScaleableLux
|
||||
- Easier config to toggle optimizations on and off
|
||||
- Improving EntityTickList further
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@ dependencies {
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
kotlin {
|
||||
target {
|
||||
compilations.configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
jvmTarget = "21"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("io.github.goooler.shadow")
|
||||
id("io.papermc.paperweight.userdev")
|
||||
id("com.diffplug.spotless")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
@@ -11,7 +10,7 @@ plugins {
|
||||
val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class)
|
||||
|
||||
java {
|
||||
javaTarget(17)
|
||||
javaTarget(21)
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
@@ -38,49 +37,36 @@ tasks {
|
||||
build {
|
||||
dependsOn(reobfJar)
|
||||
}
|
||||
}
|
||||
|
||||
spotless {
|
||||
format("misc") {
|
||||
target(project.files("*.gradle.kts", "gradle.properties", "settings.gradle.kts", "gradle/libs.versions.toml"))
|
||||
|
||||
trimTrailingWhitespace()
|
||||
indentWithSpaces(4)
|
||||
endWithNewline()
|
||||
var jarFile = file("build/libs/%s-%s.jar".format(project.name, project.version))
|
||||
var jarArtifact = artifacts.add("default", jarFile) {
|
||||
type = "jar"
|
||||
builtBy("jar")
|
||||
}
|
||||
|
||||
java {
|
||||
licenseHeaderFile("LICENSE_header.txt")
|
||||
}
|
||||
}
|
||||
|
||||
var jarFile = file("build/libs/%s-%s.jar".format(project.name, project.version))
|
||||
var jarArtifact = artifacts.add("default", jarFile) {
|
||||
type = "jar"
|
||||
builtBy("jar")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
artifact(jarArtifact)
|
||||
group = "plugins"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "gensorepo"
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
artifact(jarArtifact)
|
||||
group = "plugins"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "gensorepo"
|
||||
credentials {
|
||||
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
|
||||
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
|
||||
}
|
||||
// url to the releases maven repository
|
||||
url = uri("https://repo.gensokyoreimagined.net/")
|
||||
}
|
||||
// url to the releases maven repository
|
||||
url = uri("https://repo.gensokyoreimagined.net/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("publishMavenJavaPublicationToGensorepoRepository") {
|
||||
dependsOn("reobfJar")
|
||||
}
|
||||
tasks.named("publishMavenJavaPublicationToGensorepoRepository") {
|
||||
dependsOn("reobfJar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
group=net.gensokyoreimagined.nitori
|
||||
version=1.1-SNAPSHOT
|
||||
version=1.6-SNAPSHOT
|
||||
description=Converting patches into mixins, for the Ignite Framework
|
||||
|
||||
org.gradle.parallel=true
|
||||
|
||||
@@ -6,10 +6,10 @@ jetbrains = "24.1.0"
|
||||
ignite = "1.0.1"
|
||||
mixin = "0.8.5"
|
||||
mixinExtras = "0.3.5"
|
||||
paperweight = "1.5.11"
|
||||
shadow = "8.1.1"
|
||||
paperweight = "1.7.1"
|
||||
shadow = "8.1.2"
|
||||
spotless = "6.25.0"
|
||||
paper = "1.20.4-R0.1-SNAPSHOT"
|
||||
paper = "1.21-R0.1-SNAPSHOT"
|
||||
|
||||
[libraries]
|
||||
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains" }
|
||||
@@ -19,5 +19,5 @@ mixin = { group = "org.spongepowered", name = "mixin", version.ref = "mixin" }
|
||||
mixinExtras = { group = "io.github.llamalad7", name = "mixinextras-common", version.ref = "mixinExtras" }
|
||||
|
||||
build-paperweight = { module = "io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin", version.ref = "paperweight" }
|
||||
build-shadow = { module = "com.github.johnrengelman:shadow", version.ref = "shadow" }
|
||||
build-shadow = { module = "io.github.goooler:shadow", version.ref = "shadow" }
|
||||
build-spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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;
|
||||
|
||||
|
||||
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,6 @@
|
||||
package net.gensokyoreimagined.nitori.common.ai;
|
||||
|
||||
public interface MemoryModificationCounter {
|
||||
|
||||
long lithium$getModCount();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.gensokyoreimagined.nitori.common.ai;
|
||||
|
||||
import net.minecraft.world.entity.ai.behavior.ShufflingList;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface WeightedListIterable<U> extends Iterable<U> {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
Iterator<U> iterator();
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} over the elements in the {@param list}. This allows code to circumvent the usage
|
||||
* of streams, providing a speed-up in other areas of the game.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> Iterable<? extends T> cast(ShufflingList<T> list) {
|
||||
return ((WeightedListIterable<T>) list);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper type for an iterator over the entries of a {@link ShufflingList} which de-references the contained
|
||||
* values for consumers.
|
||||
*
|
||||
* @param <U> The value type stored in each list entry
|
||||
*/
|
||||
class ListIterator<U> implements Iterator<U> {
|
||||
private final Iterator<ShufflingList.WeightedEntry<? extends U>> inner;
|
||||
|
||||
public ListIterator(Iterator<ShufflingList.WeightedEntry<? extends U>> inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.inner.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public U next() {
|
||||
return this.inner.next().getData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,133 @@
|
||||
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.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.unapplied.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.hasLargeCollisionShape();
|
||||
}
|
||||
};
|
||||
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,14 @@
|
||||
package net.gensokyoreimagined.nitori.common.entity;
|
||||
|
||||
import net.minecraft.world.entity.ai.navigation.PathNavigation;
|
||||
|
||||
public interface NavigatingEntity {
|
||||
boolean lithium$isRegisteredToWorld();
|
||||
|
||||
void lithium$setRegisteredToWorld(PathNavigation navigation);
|
||||
|
||||
PathNavigation lithium$getRegisteredNavigation();
|
||||
|
||||
void lithium$updateNavigationRegistration();
|
||||
|
||||
}
|
||||
@@ -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,349 @@
|
||||
package net.gensokyoreimagined.nitori.common.entity.movement;
|
||||
//
|
||||
//import com.google.common.collect.AbstractIterator;
|
||||
//import net.gensokyoreimagined.nitori.common.block.BlockCountingSection;
|
||||
//import net.gensokyoreimagined.nitori.common.block.BlockStateFlags;
|
||||
//import net.gensokyoreimagined.nitori.common.shapes.VoxelShapeCaster;
|
||||
//import net.gensokyoreimagined.nitori.common.util.Pos;
|
||||
//import net.minecraft.world.level.block.state.BlockState;
|
||||
//import net.minecraft.world.level.block.Blocks;
|
||||
//import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
//import net.minecraft.world.entity.Entity;
|
||||
//import net.minecraft.world.phys.shapes.BooleanOp;
|
||||
//import net.minecraft.core.BlockPos;
|
||||
//import net.minecraft.world.phys.AABB;
|
||||
//import net.minecraft.util.Mth;
|
||||
//import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
//import net.minecraft.world.phys.shapes.Shapes;
|
||||
//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 org.jetbrains.annotations.Nullable;
|
||||
//
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.Iterator;
|
||||
//import java.util.List;
|
||||
//import java.util.NoSuchElementException;
|
||||
//
|
||||
//import static net.gensokyoreimagined.nitori.common.entity.LithiumEntityCollisions.EPSILON;
|
||||
//
|
||||
///**
|
||||
// * ChunkAwareBlockCollisionSweeper iterates over blocks in one chunk section at a time. Together with the chunk
|
||||
// * section keeping track of the amount of oversized blocks inside the number of iterations can often be reduced.
|
||||
// */
|
||||
//public class ChunkAwareBlockCollisionSweeper extends AbstractIterator<VoxelShape> {
|
||||
//
|
||||
// private final BlockPos.Mutable pos = new BlockPos.Mutable();
|
||||
//
|
||||
// /**
|
||||
// * The collision box being swept through the world.
|
||||
// */
|
||||
// private final AABB box;
|
||||
//
|
||||
// /**
|
||||
// * The VoxelShape of the collision box being swept through the world.
|
||||
// */
|
||||
// private final VoxelShape shape;
|
||||
//
|
||||
// private final Level world;
|
||||
//
|
||||
// private final CollisionContext context;
|
||||
//
|
||||
// //limits of the area without extension for oversized blocks
|
||||
// private final int minX, minY, minZ, maxX, maxY, maxZ;
|
||||
//
|
||||
// //variables prefixed with c refer to the iteration of the currently cached chunk section
|
||||
// private int chunkX, chunkYIndex, chunkZ;
|
||||
// private int cStartX, cStartZ;
|
||||
// private int cEndX, cEndZ;
|
||||
// private int cX, cY, cZ;
|
||||
//
|
||||
// private int maxHitX;
|
||||
// private int maxHitY;
|
||||
// private int maxHitZ;
|
||||
// private VoxelShape maxShape;
|
||||
// private final boolean hideLastCollision;
|
||||
//
|
||||
//
|
||||
// private int cTotalSize;
|
||||
// private int cIterated;
|
||||
//
|
||||
// private boolean sectionOversizedBlocks;
|
||||
// private ChunkAccess cachedChunk;
|
||||
// private LevelChunkSection cachedChunkSection;
|
||||
//
|
||||
// public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box) {
|
||||
// this(world, entity, box, false);
|
||||
// }
|
||||
// public ChunkAwareBlockCollisionSweeper(Level world, @Nullable Entity entity, AABB box, boolean hideLastCollision) {
|
||||
// this.box = box;
|
||||
// this.shape = Shapes.cuboid(box);
|
||||
// this.context = entity == null ? CollisionContext.absent() : CollisionContext.of(entity);
|
||||
// this.world = world;
|
||||
//
|
||||
// this.minX = Mth.floor(box.minX - EPSILON);
|
||||
// this.maxX = Mth.floor(box.maxX + EPSILON);
|
||||
// this.minY = Mth.clamp(Mth.floor(box.minY - EPSILON), Pos.BlockCoord.getMinY(this.world), Pos.BlockCoord.getMaxYInclusive(this.world));
|
||||
// this.maxY = Mth.clamp(Mth.floor(box.maxY + EPSILON), Pos.BlockCoord.getMinY(this.world), Pos.BlockCoord.getMaxYInclusive(this.world));
|
||||
// this.minZ = Mth.floor(box.minZ - EPSILON);
|
||||
// this.maxZ = Mth.floor(box.maxZ + EPSILON);
|
||||
//
|
||||
// this.chunkX = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minX));
|
||||
// this.chunkZ = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minZ));
|
||||
//
|
||||
// this.cIterated = 0;
|
||||
// this.cTotalSize = 0;
|
||||
//
|
||||
// this.maxHitX = Integer.MIN_VALUE;
|
||||
// this.maxHitY = Integer.MIN_VALUE;
|
||||
// this.maxHitZ = Integer.MIN_VALUE;
|
||||
// this.maxShape = null;
|
||||
// this.hideLastCollision = hideLastCollision;
|
||||
//
|
||||
// //decrement as first nextSection call will increment it again
|
||||
// this.chunkX--;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// public VoxelShape getLastCollision() {
|
||||
// return this.maxShape;
|
||||
// }
|
||||
//
|
||||
// public Iterator<VoxelShape> getLastCollisionIterator() {
|
||||
// return new Iterator<>() {
|
||||
// @Override
|
||||
// public boolean hasNext() {
|
||||
// return hideLastCollision && maxShape != null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public VoxelShape next() {
|
||||
// if (this.hasNext()) {
|
||||
// VoxelShape previousMaxShape = maxShape;
|
||||
// maxShape = null;
|
||||
// return previousMaxShape;
|
||||
// }
|
||||
// throw new NoSuchElementException();
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// private boolean nextSection() {
|
||||
// do {
|
||||
// do {
|
||||
// //find the coordinates of the next section inside the area expanded by 1 block on all sides
|
||||
// //note: this.minX, maxX etc are not expanded, so there are lots of +1 and -1 around.
|
||||
// if (
|
||||
// this.cachedChunk != null &&
|
||||
// this.chunkYIndex < Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world) &&
|
||||
// this.chunkYIndex < Pos.SectionYIndex.fromBlockCoord(this.world,expandMax(this.maxY))
|
||||
// ) {
|
||||
// this.chunkYIndex++;
|
||||
// this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkYIndex];
|
||||
// } else {
|
||||
// this.chunkYIndex = Mth.clamp(
|
||||
// Pos.SectionYIndex.fromBlockCoord(this.world, expandMin(this.minY)),
|
||||
// Pos.SectionYIndex.getMinYSectionIndex(this.world),
|
||||
// Pos.SectionYIndex.getMaxYSectionIndexInclusive(this.world)
|
||||
// );
|
||||
//
|
||||
// if (this.chunkX < Pos.ChunkCoord.fromBlockCoord(expandMax(this.maxX))) {
|
||||
// //first initialization takes this branch
|
||||
// this.chunkX++;
|
||||
// } else {
|
||||
// this.chunkX = Pos.ChunkCoord.fromBlockCoord(expandMin(this.minX));
|
||||
//
|
||||
// if (this.chunkZ < Pos.ChunkCoord.fromBlockCoord(expandMax(this.maxZ))) {
|
||||
// this.chunkZ++;
|
||||
// } else {
|
||||
// return false; //no more sections to iterate
|
||||
// }
|
||||
// }
|
||||
// this.cachedChunk = this.world.getChunk(this.chunkX, this.chunkZ, ChunkStatus.FULL, false);
|
||||
// if (this.cachedChunk != null) {
|
||||
// this.cachedChunkSection = this.cachedChunk.getSectionArray()[this.chunkYIndex];
|
||||
// }
|
||||
// }
|
||||
// //skip empty chunks and empty chunk sections
|
||||
// } while (this.cachedChunk == null || this.cachedChunkSection == null || this.cachedChunkSection.isEmpty());
|
||||
//
|
||||
// this.sectionOversizedBlocks = hasChunkSectionOversizedBlocks(this.cachedChunk, this.chunkYIndex);
|
||||
//
|
||||
// int sizeExtension = this.sectionOversizedBlocks ? 1 : 0;
|
||||
//
|
||||
// this.cEndX = Math.min(this.maxX + sizeExtension, Pos.BlockCoord.getMaxInSectionCoord(this.chunkX));
|
||||
// int cEndY = Math.min(this.maxY + sizeExtension, Pos.BlockCoord.getMaxYInSectionIndex(this.world, this.chunkYIndex));
|
||||
// this.cEndZ = Math.min(this.maxZ + sizeExtension, Pos.BlockCoord.getMaxInSectionCoord(this.chunkZ));
|
||||
//
|
||||
// this.cStartX = Math.max(this.minX - sizeExtension, Pos.BlockCoord.getMinInSectionCoord(this.chunkX));
|
||||
// int cStartY = Math.max(this.minY - sizeExtension, Pos.BlockCoord.getMinYInSectionIndex(this.world, this.chunkYIndex));
|
||||
// this.cStartZ = Math.max(this.minZ - sizeExtension, Pos.BlockCoord.getMinInSectionCoord(this.chunkZ));
|
||||
// this.cX = this.cStartX;
|
||||
// this.cY = cStartY;
|
||||
// this.cZ = this.cStartZ;
|
||||
//
|
||||
// this.cTotalSize = (this.cEndX - this.cStartX + 1) * (cEndY - cStartY + 1) * (this.cEndZ - this.cStartZ + 1);
|
||||
// //skip completely empty section iterations
|
||||
// } while (this.cTotalSize == 0);
|
||||
// this.cIterated = 0;
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Advances the sweep forward until finding a block with a box-colliding VoxelShape
|
||||
// *
|
||||
// * @return null if no VoxelShape is left in the area, otherwise the next VoxelShape
|
||||
// */
|
||||
// @Override
|
||||
// public VoxelShape computeNext() {
|
||||
// while (true) {
|
||||
// if (this.cIterated >= this.cTotalSize) {
|
||||
// if (!this.nextSection()) {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.cIterated++;
|
||||
//
|
||||
//
|
||||
// final int x = this.cX;
|
||||
// final int y = this.cY;
|
||||
// final int z = this.cZ;
|
||||
//
|
||||
// //The iteration order within a chunk section is chosen so that it causes a mostly linear array access in the storage.
|
||||
// //In net.minecraft.world.chunk.PalettedContainer.toIndex x gets the 4 least significant bits, z the 4 above, and y the 4 even higher ones.
|
||||
// //Linearly accessing arrays is faster than other access patterns.
|
||||
// if (this.cX < this.cEndX) {
|
||||
// this.cX++;
|
||||
// } else if (this.cZ < this.cEndZ) {
|
||||
// this.cX = this.cStartX;
|
||||
// this.cZ++;
|
||||
// } else {
|
||||
// this.cX = this.cStartX;
|
||||
// this.cZ = this.cStartZ;
|
||||
// this.cY++;
|
||||
// //stop condition was already checked using this.cIterated at the start of the method
|
||||
// }
|
||||
//
|
||||
// //using < minX and > maxX instead of <= and >= in vanilla, because minX, maxX are the coordinates
|
||||
// //of the box that wasn't extended for oversized blocks yet.
|
||||
// final int edgesHit = this.sectionOversizedBlocks ?
|
||||
// (x < this.minX || x > this.maxX ? 1 : 0) +
|
||||
// (y < this.minY || y > this.maxY ? 1 : 0) +
|
||||
// (z < this.minZ || z > this.maxZ ? 1 : 0) : 0;
|
||||
//
|
||||
// if (edgesHit == 3) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// final BlockState state = this.cachedChunkSection.getBlockState(x & 15, y & 15, z & 15);
|
||||
//
|
||||
// if (!canInteractWithBlock(state, edgesHit)) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// this.pos.set(x, y, z);
|
||||
//
|
||||
// VoxelShape collisionShape = state.getCollisionShape(this.world, this.pos, this.context);
|
||||
//
|
||||
// if (collisionShape != Shapes.empty() && collisionShape != null /*collisionShape should never be null, but we received crash reports.*/) {
|
||||
// VoxelShape collidedShape = getCollidedShape(this.box, this.shape, collisionShape, x, y, z);
|
||||
// if (collidedShape != null) {
|
||||
// if (z >= this.maxHitZ && (z > this.maxHitZ || y >= this.maxHitY && (y > this.maxHitY || x > this.maxHitX))) {
|
||||
// this.maxHitX = x;
|
||||
// this.maxHitY = y;
|
||||
// this.maxHitZ = z;
|
||||
// //Always make sure the shape at the maximum position is the last one returned, because
|
||||
// // the last shape has a different 1e-7 behavior (no next shape that clips movement to 0).
|
||||
// // This does affect certain contraptions: https://github.com/CaffeineMC/lithium-fabric/issues/443
|
||||
// VoxelShape previousMaxShape = this.maxShape;
|
||||
// this.maxShape = collidedShape;
|
||||
// if (previousMaxShape != null) {
|
||||
// return previousMaxShape;
|
||||
// }
|
||||
// } else {
|
||||
// return collidedShape;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (!this.hideLastCollision && this.maxShape != null) {
|
||||
// VoxelShape previousMaxShape = this.maxShape;
|
||||
// this.maxShape = null;
|
||||
// return previousMaxShape;
|
||||
// }
|
||||
//
|
||||
// return this.endOfData();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * This is an artifact from vanilla which is used to avoid testing shapes in the extended portion of a volume
|
||||
// * unless they are a shape which exceeds their voxel. Pistons must be special-cased here.
|
||||
// *
|
||||
// * @return True if the shape can be interacted with at the given edge boundary
|
||||
// */
|
||||
// private static boolean canInteractWithBlock(BlockState state, int edgesHit) {
|
||||
// return (edgesHit != 1 || state.exceedsCube()) && (edgesHit != 2 || state.getBlock() == Blocks.MOVING_PISTON);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Checks if the {@param entityShape} or {@param entityBox} intersects the given {@param shape} which is translated
|
||||
// * to the given position. This is a very specialized implementation which tries to avoid going through VoxelShape
|
||||
// * for full-cube shapes.
|
||||
// *
|
||||
// * @return A {@link VoxelShape} which contains the shape representing that which was collided with, otherwise null
|
||||
// */
|
||||
// private static VoxelShape getCollidedShape(AABB entityBox, VoxelShape entityShape, VoxelShape shape, int x, int y, int z) {
|
||||
// if (shape == Shapes.fullCube()) {
|
||||
// return entityBox.intersects(x, y, z, x + 1.0, y + 1.0, z + 1.0) ? shape.offset(x, y, z) : null;
|
||||
// }
|
||||
// if (shape instanceof VoxelShapeCaster) {
|
||||
// if (((VoxelShapeCaster) shape).intersects(entityBox, x, y, z)) {
|
||||
// return shape.offset(x, y, z);
|
||||
// } else {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// shape = shape.offset(x, y, z);
|
||||
//
|
||||
// if (Shapes.matchesAnywhere(shape, entityShape, BooleanOp.AND)) {
|
||||
// return shape;
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// private static int expandMin(int coord) {
|
||||
// return coord - 1;
|
||||
// }
|
||||
// private static int expandMax(int coord) {
|
||||
// return coord + 1;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Checks the cached information whether the {@param chunkY} section of the {@param chunk} has oversized blocks.
|
||||
// *
|
||||
// * @return Whether there are any oversized blocks in the chunk section.
|
||||
// */
|
||||
// private static boolean hasChunkSectionOversizedBlocks(ChunkAccess chunk, int chunkY) {
|
||||
// if (BlockStateFlags.ENABLED) {
|
||||
// LevelChunkSection section = chunk.getSectionArray()[chunkY];
|
||||
// return section != null && ((BlockCountingSection) section).lithium$mayContainAny(BlockStateFlags.OVERSIZED_SHAPE);
|
||||
// }
|
||||
// return true; //like vanilla, assume that a chunk section has oversized blocks, when the section mixin isn't loaded
|
||||
// }
|
||||
//
|
||||
// public List<VoxelShape> collectAll() {
|
||||
// ArrayList<VoxelShape> collisions = new ArrayList<>();
|
||||
//
|
||||
// while (this.hasNext()) {
|
||||
// collisions.add(this.next());
|
||||
// }
|
||||
//
|
||||
// return collisions;
|
||||
// }
|
||||
//}
|
||||
@@ -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,116 @@
|
||||
package net.gensokyoreimagined.nitori.common.math.random;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.levelgen.BitRandomSource;
|
||||
import net.minecraft.world.level.levelgen.LegacyRandomSource;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
|
||||
|
||||
import java.util.random.RandomGenerator;
|
||||
import java.util.random.RandomGeneratorFactory;
|
||||
|
||||
public class RandomGeneratorRandom implements BitRandomSource {
|
||||
private static final RandomGeneratorFactory<RandomGenerator.SplittableGenerator> RANDOM_GENERATOR_FACTORY = RandomGeneratorFactory.of("L64X128MixRandom");
|
||||
|
||||
private static final int INT_BITS = 48;
|
||||
private static final long SEED_MASK = 0xFFFFFFFFFFFFL;
|
||||
private static final long MULTIPLIER = 25214903917L;
|
||||
private static final long INCREMENT = 11L;
|
||||
|
||||
private long seed;
|
||||
private RandomGenerator.SplittableGenerator randomGenerator;
|
||||
|
||||
public RandomGeneratorRandom(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fork() {
|
||||
return new RandomGeneratorRandom(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PositionalRandomFactory forkPositional() {
|
||||
return new Splitter(this.nextLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeed(long seed) {
|
||||
this.seed = seed;
|
||||
this.randomGenerator = RANDOM_GENERATOR_FACTORY.create(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next(int bits) {
|
||||
// >>> instead of Mojang's >> fixes MC-239059
|
||||
return (int) ((seed * MULTIPLIER + INCREMENT & SEED_MASK) >>> INT_BITS - bits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
return randomGenerator.nextInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return randomGenerator.nextInt(bound);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() {
|
||||
return randomGenerator.nextLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextBoolean() {
|
||||
return randomGenerator.nextBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float nextFloat() {
|
||||
return randomGenerator.nextFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextDouble() {
|
||||
return randomGenerator.nextDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextGaussian() {
|
||||
return randomGenerator.nextGaussian();
|
||||
}
|
||||
|
||||
private record Splitter(long seed) implements PositionalRandomFactory {
|
||||
@Override
|
||||
public RandomSource at(int x, int y, int z) {
|
||||
return new RandomGeneratorRandom(Mth.getSeed(x, y, z) ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromHashOf(String seed) {
|
||||
return new RandomGeneratorRandom((long) seed.hashCode() ^ this.seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource fromSeed(long seed) {
|
||||
return new RandomGeneratorRandom(seed ^ this.seed);
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public Random split(long seed) {
|
||||
return new RandomGeneratorRandom(seed);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@Override
|
||||
@VisibleForTesting
|
||||
public void parityConfigString(StringBuilder info) {
|
||||
info.append("RandomGeneratorRandom$Splitter{").append(this.seed).append("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,19 @@
|
||||
package net.gensokyoreimagined.nitori.common.shapes;
|
||||
|
||||
//import net.minecraft.world.phys.shapes.BooleanOp;
|
||||
//import net.minecraft.world.phys.AABB;
|
||||
//import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
//
|
||||
//
|
||||
//public interface VoxelShapeCaster {
|
||||
// /**
|
||||
// * Checks whether an entity's bounding box collides with this shape translated to the given coordinates.
|
||||
// *
|
||||
// * @param box The entity's bounding box
|
||||
// * @param blockX The x-coordinate of this shape
|
||||
// * @param blockY The y-coordinate of this shape
|
||||
// * @param blockZ The z-coordinate of this shape
|
||||
// * @return True if the box intersects with this shape, otherwise false
|
||||
// */
|
||||
// boolean intersects(AABB box, double blockX, double blockY, double blockZ);
|
||||
//}
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.common.state;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.common.state;
|
||||
|
||||
import it.unimi.dsi.fastutil.Hash;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.common.state;
|
||||
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Many of the column and row key arrays in block state tables will be duplicated, leading to an unnecessary waste of
|
||||
* memory. Since we have very limited options for trying to construct more optimized table types without throwing
|
||||
* maintainability or mod compatibility out the window, this class acts as a dirty way to find and de-duplicate arrays
|
||||
* after we construct our table types.
|
||||
* <p>
|
||||
* While this global cache does not provide the ability to remove or clear entries from it, the reality is that it
|
||||
* shouldn't matter because block state tables are only initialized once and remain loaded for the entire lifetime of
|
||||
* the game. Even in the event of classloader pre-boot shenanigans, we still shouldn't leak memory as our cache will be
|
||||
* dropped along with the rest of the loaded classes when the class loader is reaped.
|
||||
*/
|
||||
public class StatePropertyTableCache {
|
||||
public static final FastImmutableTableCache<Property<?>, Comparable<?>, BlockState> BLOCK_STATE_TABLE =
|
||||
new FastImmutableTableCache<>();
|
||||
|
||||
public static final FastImmutableTableCache<Property<?>, Comparable<?>, FluidState> FLUID_STATE_TABLE =
|
||||
new FastImmutableTableCache<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <S, O> FastImmutableTableCache<Property<?>, Comparable<?>, S> getTableCache(O owner) {
|
||||
if (owner instanceof Block) {
|
||||
return (FastImmutableTableCache<Property<?>, Comparable<?>, S>) BLOCK_STATE_TABLE;
|
||||
} else if (owner instanceof Fluid) {
|
||||
return (FastImmutableTableCache<Property<?>, Comparable<?>, S>) FLUID_STATE_TABLE;
|
||||
} else {
|
||||
throw new IllegalArgumentException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.gensokyoreimagined.nitori.common.util;
|
||||
|
||||
/**
|
||||
* Pre-initialized constants to avoid unnecessary allocations.
|
||||
*/
|
||||
public final class ArrayConstants {
|
||||
private ArrayConstants() {}
|
||||
|
||||
public static final int[] EMPTY = new int[0];
|
||||
public static final int[] ZERO = new int[]{0};
|
||||
}
|
||||
@@ -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,106 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.collections;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class LazyList<T> extends AbstractList<T> {
|
||||
|
||||
private final ArrayList<T> delegate;
|
||||
private Iterator<T> iterator;
|
||||
|
||||
public LazyList(ArrayList<T> delegate, Iterator<T> iterator) {
|
||||
this.delegate = delegate;
|
||||
this.iterator = iterator;
|
||||
}
|
||||
|
||||
private boolean produceToIndex(int n) {
|
||||
n -= this.delegate.size();
|
||||
if (n >= 0 && this.iterator != null) {
|
||||
while (this.iterator.hasNext()) {
|
||||
this.delegate.add(this.iterator.next());
|
||||
if (--n < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this.iterator = null;
|
||||
}
|
||||
return n < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
this.produceToIndex(index);
|
||||
return this.delegate.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
this.produceToIndex(Integer.MAX_VALUE);
|
||||
return this.delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<T> iterator() {
|
||||
return new Iterator<T>() {
|
||||
private int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return LazyList.this.produceToIndex(this.index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return LazyList.this.get(this.index++);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
this.produceToIndex(index);
|
||||
return this.delegate.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
this.produceToIndex(index - 1);
|
||||
this.delegate.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
this.produceToIndex(index);
|
||||
return this.delegate.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.delegate.clear();
|
||||
this.iterator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
this.produceToIndex(Integer.MAX_VALUE);
|
||||
return this.delegate.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return !this.produceToIndex(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends T> c) {
|
||||
if (c.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
this.produceToIndex(index - 1);
|
||||
return this.delegate.addAll(index, c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MaskedList<E> extends AbstractList<E> {
|
||||
private final ObjectArrayList<E> allElements;
|
||||
private final BitSet visibleMask;
|
||||
private final Object2IntOpenHashMap<E> element2Index;
|
||||
private final boolean defaultVisibility;
|
||||
private int numCleared;
|
||||
|
||||
public MaskedList(ObjectArrayList<E> allElements, boolean defaultVisibility) {
|
||||
this.allElements = new ObjectArrayList<>();
|
||||
this.visibleMask = new BitSet();
|
||||
this.defaultVisibility = defaultVisibility;
|
||||
this.element2Index = new Object2IntOpenHashMap<>();
|
||||
this.element2Index.defaultReturnValue(-1);
|
||||
|
||||
this.addAll(allElements);
|
||||
}
|
||||
|
||||
public MaskedList() {
|
||||
this(new ObjectArrayList<>(), true);
|
||||
}
|
||||
|
||||
public int totalSize() {
|
||||
return this.allElements.size();
|
||||
}
|
||||
|
||||
|
||||
public void addOrSet(E element, boolean visible) {
|
||||
int index = this.element2Index.getInt(element);
|
||||
if (index != -1) {
|
||||
this.visibleMask.set(index, visible);
|
||||
} else {
|
||||
this.add(element);
|
||||
this.setVisible(element, visible);
|
||||
}
|
||||
}
|
||||
|
||||
public void setVisible(E element, final boolean visible) {
|
||||
int index = this.element2Index.getInt(element);
|
||||
if (index != -1) {
|
||||
this.visibleMask.set(index, visible);
|
||||
}
|
||||
//ignore when the element is not in the collection
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return new Iterator<>() {
|
||||
int nextIndex = 0;
|
||||
int cachedNext = -1;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return (this.cachedNext = MaskedList.this.visibleMask.nextSetBit(this.nextIndex)) != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E next() {
|
||||
int index = this.cachedNext;
|
||||
this.cachedNext = -1;
|
||||
this.nextIndex = index + 1;
|
||||
return MaskedList.this.allElements.get(index);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<E> spliterator() {
|
||||
return new Spliterators.AbstractSpliterator<E>(Long.MAX_VALUE, Spliterator.ORDERED | Spliterator.NONNULL) {
|
||||
int nextIndex = 0;
|
||||
|
||||
@Override
|
||||
public boolean tryAdvance(Consumer<? super E> action) {
|
||||
int index = MaskedList.this.visibleMask.nextSetBit(this.nextIndex);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
this.nextIndex = index + 1;
|
||||
action.accept(MaskedList.this.allElements.get(index));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
int oldIndex = this.element2Index.put(e, this.allElements.size());
|
||||
if (oldIndex != -1) {
|
||||
throw new IllegalStateException("MaskedList must not contain duplicates! Trying to add " + e + " but it is already present at index " + oldIndex + ". Current size: " + this.allElements.size());
|
||||
}
|
||||
this.visibleMask.set(this.allElements.size(), this.defaultVisibility);
|
||||
return this.allElements.add(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
int index = this.element2Index.removeInt(o);
|
||||
if (index == -1) {
|
||||
return false;
|
||||
}
|
||||
this.visibleMask.clear(index);
|
||||
this.allElements.set(index, null);
|
||||
this.numCleared++;
|
||||
|
||||
|
||||
if (this.numCleared * 2 > this.allElements.size()) {
|
||||
ObjectArrayList<E> clonedElements = this.allElements.clone();
|
||||
BitSet clonedVisibleMask = (BitSet) this.visibleMask.clone();
|
||||
this.allElements.clear();
|
||||
this.visibleMask.clear();
|
||||
this.element2Index.clear();
|
||||
for (int i = 0; i < clonedElements.size(); i++) {
|
||||
E element = clonedElements.get(i);
|
||||
int newIndex = this.allElements.size();
|
||||
this.allElements.add(element);
|
||||
this.visibleMask.set(newIndex, clonedVisibleMask.get(i));
|
||||
this.element2Index.put(element, newIndex);
|
||||
}
|
||||
this.numCleared = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E get(int index) {
|
||||
if (index < 0 || index >= this.size()) {
|
||||
throw new IndexOutOfBoundsException(index);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (index >= 0) {
|
||||
index--;
|
||||
i = this.visibleMask.nextSetBit(i + 1);
|
||||
}
|
||||
return this.allElements.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.visibleMask.cardinality();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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.common.util.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.HashCommon;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A lossy hashtable implementation that stores a mapping between an object and a boolean.
|
||||
* <p>
|
||||
* Any hash collisions will result in an overwrite: this is safe because the correct value can always be recomputed,
|
||||
* given that the given operator is deterministic.
|
||||
* <p>
|
||||
* This implementation is safe to use from multiple threads
|
||||
*/
|
||||
public final class Object2BooleanCacheTable<T> {
|
||||
private final int mask;
|
||||
|
||||
private final Node<T>[] nodes;
|
||||
|
||||
private final Predicate<T> operator;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object2BooleanCacheTable(int capacity, Predicate<T> operator) {
|
||||
int capacity1 = Mth.smallestEncompassingPowerOfTwo(capacity);
|
||||
this.mask = capacity1 - 1;
|
||||
|
||||
this.nodes = (Node<T>[]) new Node[capacity1];
|
||||
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
private static <T> int hash(T key) {
|
||||
return HashCommon.mix(key.hashCode());
|
||||
}
|
||||
|
||||
public boolean get(T key) {
|
||||
int idx = hash(key) & this.mask;
|
||||
|
||||
Node<T> node = this.nodes[idx];
|
||||
if (node != null && key.equals(node.key)) {
|
||||
return node.value;
|
||||
}
|
||||
|
||||
boolean test = this.operator.test(key);
|
||||
this.nodes[idx] = new Node<>(key, test);
|
||||
|
||||
return test;
|
||||
}
|
||||
|
||||
static class Node<T> {
|
||||
final T key;
|
||||
final boolean value;
|
||||
|
||||
Node(T key, boolean value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,8 @@
|
||||
package net.gensokyoreimagined.nitori.common.util.deduplication;
|
||||
|
||||
public interface LithiumInternerWrapper<T> {
|
||||
|
||||
T lithium$getCanonical(T value);
|
||||
|
||||
void lithium$deleteCanonical(T value);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.gensokyoreimagined.nitori.common.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,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
|
||||
@@ -12,20 +12,17 @@
|
||||
//
|
||||
// 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.core;
|
||||
package net.gensokyoreimagined.nitori.common.world;
|
||||
|
||||
import com.destroystokyo.paper.util.maplist.EntityList;
|
||||
import io.papermc.paper.world.ChunkEntitySlices;
|
||||
import net.gensokyoreimagined.nitori.access.IMixinChunkEntitySlicesAccess;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
@Mixin(ChunkEntitySlices.class)
|
||||
public abstract class MixinChunkEntitySlices implements IMixinChunkEntitySlicesAccess {
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Override
|
||||
@Final
|
||||
@Accessor
|
||||
public abstract EntityList getEntities();
|
||||
// Taken from Lithium
|
||||
// https://github.com/CaffeineMC/lithium-fabric/blob/427dd75ffc922cc1858c1db4b283cc54744567e0/src/main/java/me/jellysquid/mods/lithium/common/world/ChunkRandomSource.java
|
||||
|
||||
public interface ChunkRandomSource {
|
||||
/**
|
||||
* Alternative implementation of {@link net.minecraft.server.level.ServerLevel#getBlockRandomPos(int, int, int, int)} which does not allocate
|
||||
* a new {@link BlockPos}.
|
||||
*/
|
||||
void nitori$getRandomPosInChunk(int x, int y, int z, int mask, BlockPos.MutableBlockPos out);
|
||||
}
|
||||
@@ -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,9 @@
|
||||
package net.gensokyoreimagined.nitori.common.world;
|
||||
|
||||
import net.minecraft.world.entity.Mob;
|
||||
|
||||
public interface ServerWorldExtended {
|
||||
void lithium$setNavigationActive(Mob mobEntity);
|
||||
|
||||
void lithium$setNavigationInactive(Mob mobEntity);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
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.EntityTrackingSectionAccessor;
|
||||
import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -12,16 +12,11 @@
|
||||
//
|
||||
// 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.access;
|
||||
package net.gensokyoreimagined.nitori.common.world.blockentity;
|
||||
|
||||
import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
public interface IMixinChunkMap_TrackedEntityAccess {
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
Entity getEntity();
|
||||
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
void callUpdatePlayers(PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates);
|
||||
}
|
||||
public interface BlockEntityGetter {
|
||||
BlockEntity lithium$getLoadedExistingBlockEntity(BlockPos pos);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.blockentity;
|
||||
|
||||
public interface SupportCache {
|
||||
boolean lithium$isSupported();
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.blockview;
|
||||
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.CollisionGetter;
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public record SingleBlockBlockView(BlockState state, BlockPos pos) implements BlockGetter, CollisionGetter {
|
||||
public static SingleBlockBlockView of(BlockState blockState, BlockPos blockPos) {
|
||||
return new SingleBlockBlockView(blockState, blockPos.immutable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
if (pos.equals(this.pos())) {
|
||||
return this.state();
|
||||
} else {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
if (pos.equals(this.pos())) {
|
||||
return this.state().getFluidState();
|
||||
} else {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinBuildHeight() {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldBorder getWorldBorder() {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlockGetter getChunkForCollisions(int chunkX, int chunkZ) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnobstructed(BlockState state, BlockPos pos, CollisionContext context) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnobstructed(Entity entity) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean noCollision(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<VoxelShape> getCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<VoxelShape> getBlockCollisions(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collidesWithSuffocatingBlock(@Nullable Entity entity, AABB box) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Vec3> findFreePosition(@Nullable Entity entity, VoxelShape shape, Vec3 target, double x, double y, double z) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
public static class SingleBlockViewException extends RuntimeException {
|
||||
|
||||
public static final SingleBlockViewException INSTANCE = new SingleBlockViewException();
|
||||
|
||||
private SingleBlockViewException() {
|
||||
this.setStackTrace(new StackTraceElement[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
this.setStackTrace(new StackTraceElement[0]);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@javax.annotation.Nullable
|
||||
@Override
|
||||
public BlockState getBlockStateIfLoaded(@NotNull BlockPos block) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
|
||||
@javax.annotation.Nullable
|
||||
@Override
|
||||
public FluidState getFluidIfLoaded(@NotNull BlockPos block) {
|
||||
throw SingleBlockViewException.INSTANCE;
|
||||
}
|
||||
}
|
||||
@@ -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.removed.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);
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,14 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.interests;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface PointOfInterestSetExtended {
|
||||
void lithium$collectMatchingPoints(Predicate<Holder<PoiType>> type, PoiManager.Occupancy status,
|
||||
Consumer<PoiRecord> consumer);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.interests.iterator;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public record SinglePointOfInterestTypeFilter(
|
||||
Holder<PoiType> type) implements Predicate<Holder<PoiType>> {
|
||||
|
||||
@Override
|
||||
public boolean test(Holder<PoiType> other) {
|
||||
return this.type == other;
|
||||
}
|
||||
|
||||
public Holder<PoiType> getType() {
|
||||
return this.type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.interests.types;
|
||||
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class PointOfInterestTypeHelper {
|
||||
private static Predicate<BlockState> POI_BLOCKSTATE_PREDICATE;
|
||||
|
||||
|
||||
public static void init(Set<BlockState> types) {
|
||||
if (POI_BLOCKSTATE_PREDICATE != null) {
|
||||
throw new IllegalStateException("Already initialized");
|
||||
}
|
||||
|
||||
POI_BLOCKSTATE_PREDICATE = types::contains;
|
||||
}
|
||||
|
||||
public static boolean mayHavePoi(LevelChunkSection chunkSection) {
|
||||
return chunkSection.maybeHas(POI_BLOCKSTATE_PREDICATE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.listeners;
|
||||
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
import net.minecraft.world.level.border.BorderChangeListener;
|
||||
|
||||
public interface WorldBorderListenerOnce extends BorderChangeListener {
|
||||
|
||||
void lithium$onWorldBorderShapeChange(WorldBorder worldBorder);
|
||||
|
||||
default void onAreaReplaced(WorldBorder border) {
|
||||
this.lithium$onWorldBorderShapeChange(border);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSizeSet(WorldBorder border, double size) {
|
||||
this.lithium$onWorldBorderShapeChange(border);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSizeLerping(WorldBorder border, double fromSize, double toSize, long time) {
|
||||
this.lithium$onWorldBorderShapeChange(border);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderCenterSet(WorldBorder border, double centerX, double centerZ) {
|
||||
this.lithium$onWorldBorderShapeChange(border);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSetWarningTime(WorldBorder border, int warningTime) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSetWarningBlocks(WorldBorder border, int warningBlockDistance) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
default void onBorderSetDamageSafeZOne(WorldBorder border, double safeZoneRadius) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package net.gensokyoreimagined.nitori.common.world.scheduler;
|
||||
|
||||
// ChunkTickSchedulerMixin needs this
|
||||
|
||||
|
||||
|
||||
//import it.unimi.dsi.fastutil.HashCommon;
|
||||
//import net.minecraft.world.ticks.ScheduledTick;
|
||||
//
|
||||
//import java.util.*;
|
||||
//
|
||||
//public class OrderedTickQueue<T> extends AbstractQueue<ScheduledTick<T>> {
|
||||
// private static final int INITIAL_CAPACITY = 16;
|
||||
// private static final Comparator<ScheduledTick<?>> COMPARATOR = Comparator.comparingLong(ScheduledTick::subTickOrder);
|
||||
//
|
||||
// private ScheduledTick<T>[] arr;
|
||||
//
|
||||
// private int lastIndexExclusive;
|
||||
// private int firstIndex;
|
||||
//
|
||||
// private long currentMaxSubTickOrder = Long.MIN_VALUE;
|
||||
// private boolean isSorted;
|
||||
// private ScheduledTick<T> unsortedPeekResult;
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// public OrderedTickQueue(int capacity) {
|
||||
// this.arr = (ScheduledTick<T>[]) new ScheduledTick[capacity];
|
||||
// this.lastIndexExclusive = 0;
|
||||
// this.isSorted = true;
|
||||
// this.unsortedPeekResult = null;
|
||||
// this.firstIndex = 0;
|
||||
// }
|
||||
//
|
||||
// public OrderedTickQueue() {
|
||||
// this(INITIAL_CAPACITY);
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void clear() {
|
||||
// Arrays.fill(this.arr, null);
|
||||
// this.lastIndexExclusive = 0;
|
||||
// this.firstIndex = 0;
|
||||
// this.currentMaxSubTickOrder = Long.MIN_VALUE;
|
||||
// this.isSorted = true;
|
||||
// this.unsortedPeekResult = null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public Iterator<ScheduledTick<T>> iterator() {
|
||||
// if (this.isEmpty()) {
|
||||
// return Collections.emptyIterator();
|
||||
// }
|
||||
// this.sort();
|
||||
// return new Iterator<>() {
|
||||
// int nextIndex = OrderedTickQueue.this.firstIndex;
|
||||
//
|
||||
// @Override
|
||||
// public boolean hasNext() {
|
||||
// return this.nextIndex < OrderedTickQueue.this.lastIndexExclusive;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public ScheduledTick<T> next() {
|
||||
// return OrderedTickQueue.this.arr[this.nextIndex++];
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public ScheduledTick<T> poll() {
|
||||
// if (this.isEmpty()) {
|
||||
// return null;
|
||||
// }
|
||||
// if (!this.isSorted) {
|
||||
// this.sort();
|
||||
// }
|
||||
// ScheduledTick<T> nextTick;
|
||||
// int polledIndex = this.firstIndex++;
|
||||
// ScheduledTick<T>[] ticks = this.arr;
|
||||
// nextTick = ticks[polledIndex];
|
||||
// ticks[polledIndex] = null;
|
||||
// return nextTick;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public ScheduledTick<T> peek() {
|
||||
// if (!this.isSorted) {
|
||||
// return this.unsortedPeekResult;
|
||||
// } else if (this.lastIndexExclusive > this.firstIndex) {
|
||||
// return this.getTickAtIndex(this.firstIndex);
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// public boolean offer(ScheduledTick<T> tick) {
|
||||
// if (this.lastIndexExclusive >= this.arr.length) {
|
||||
// //todo remove consumed elements first
|
||||
// this.arr = copyArray(this.arr, HashCommon.nextPowerOfTwo(this.arr.length + 1));
|
||||
// }
|
||||
// if (tick.subTickOrder() <= this.currentMaxSubTickOrder) {
|
||||
// //Set to unsorted instead of slowing down the insertion
|
||||
// //This is rare but may happen in bulk
|
||||
// //Sorting later needs O(n*log(n)) time, but it only needs to happen when unordered insertion needs to happen
|
||||
// //Therefore it is better than n times log(n) time of the PriorityQueue that happens on ordered insertion too
|
||||
// ScheduledTick<T> firstTick = this.isSorted ? this.size() > 0 ? this.arr[this.firstIndex] : null : this.unsortedPeekResult;
|
||||
// this.isSorted = false;
|
||||
// this.unsortedPeekResult = firstTick == null || tick.subTickOrder() < firstTick.subTickOrder() ? tick : firstTick;
|
||||
// } else {
|
||||
// this.currentMaxSubTickOrder = tick.subTickOrder();
|
||||
// }
|
||||
// this.arr[this.lastIndexExclusive++] = tick;
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// public int size() {
|
||||
// return this.lastIndexExclusive - this.firstIndex;
|
||||
// }
|
||||
//
|
||||
// private void handleCompaction(int size) {
|
||||
// // Only compact the array if it is less than 50% filled
|
||||
// if (this.arr.length > INITIAL_CAPACITY && size < this.arr.length / 2) {
|
||||
// this.arr = copyArray(this.arr, size);
|
||||
// } else {
|
||||
// // Fill the unused array elements with nulls to release our references to the elements in it
|
||||
// Arrays.fill(this.arr, size, this.arr.length, null);
|
||||
// }
|
||||
//
|
||||
// this.firstIndex = 0;
|
||||
// this.lastIndexExclusive = size;
|
||||
//
|
||||
// if (size == 0 || !this.isSorted) {
|
||||
// this.currentMaxSubTickOrder = Long.MIN_VALUE;
|
||||
// } else {
|
||||
// ScheduledTick<T> tick = this.arr[size - 1];
|
||||
// this.currentMaxSubTickOrder = tick == null ? Long.MIN_VALUE : tick.subTickOrder();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public void sort() {
|
||||
// if (this.isSorted) {
|
||||
// return;
|
||||
// }
|
||||
// this.removeNullsAndConsumed();
|
||||
// Arrays.sort(this.arr, this.firstIndex, this.lastIndexExclusive, COMPARATOR);
|
||||
// this.isSorted = true;
|
||||
// this.unsortedPeekResult = null;
|
||||
// }
|
||||
//
|
||||
// public void removeNullsAndConsumed() {
|
||||
// int src = this.firstIndex;
|
||||
// int dst = 0;
|
||||
// while (src < this.lastIndexExclusive) {
|
||||
// ScheduledTick<T> ScheduledTick = this.arr[src];
|
||||
// if (ScheduledTick != null) {
|
||||
// this.arr[dst] = ScheduledTick;
|
||||
// dst++;
|
||||
// }
|
||||
// src++;
|
||||
// }
|
||||
// this.handleCompaction(dst);
|
||||
// }
|
||||
//
|
||||
// public ScheduledTick<T> getTickAtIndex(int index) {
|
||||
// if (!this.isSorted) {
|
||||
// throw new IllegalStateException("Unexpected access on unsorted queue!");
|
||||
// }
|
||||
// return this.arr[index];
|
||||
// }
|
||||
//
|
||||
// public void setTickAtIndex(int index, ScheduledTick<T> tick) {
|
||||
// if (!this.isSorted) {
|
||||
// throw new IllegalStateException("Unexpected access on unsorted queue!");
|
||||
// }
|
||||
// this.arr[index] = tick;
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// private static <T> ScheduledTick<T>[] copyArray(ScheduledTick<T>[] src, int size) {
|
||||
// final ScheduledTick<T>[] copy = new ScheduledTick[Math.max(INITIAL_CAPACITY, size)];
|
||||
//
|
||||
// if (size != 0) {
|
||||
// System.arraycopy(src, 0, copy, 0, Math.min(src.length, size));
|
||||
// }
|
||||
//
|
||||
// return copy;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean isEmpty() {
|
||||
// return this.lastIndexExclusive <= this.firstIndex;
|
||||
// }
|
||||
//}
|
||||
@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.Map;
|
||||
public final class PluginCompatibilityCitizens extends BasePluginCompatibility {
|
||||
private static final String CITIZENS_PLUGIN_NAME = "Citizens";
|
||||
|
||||
private static final ClassReflectionReferenceResolver citizensPluginCitizensEntityTrackerClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.util.CitizensEntityTracker");
|
||||
private static final ClassReflectionReferenceResolver citizensPluginCitizensEntityTrackerClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R4.util.CitizensEntityTracker");
|
||||
|
||||
private static final ClassReflectionReferenceResolver citizensPluginEntityHumanNPCClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R3.entity.EntityHumanNPC");
|
||||
private static final ClassReflectionReferenceResolver citizensPluginEntityHumanNPCClassResolver = new ClassReflectionReferenceResolver("net.citizensnpcs.nms.v1_20_R4.entity.EntityHumanNPC");
|
||||
|
||||
PluginCompatibilityCitizens() {
|
||||
super(new String[]{CITIZENS_PLUGIN_NAME});
|
||||
|
||||
@@ -1,170 +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.core;
|
||||
|
||||
import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSets;
|
||||
//import net.gensokyoreimagined.nitori.config.NitoriConfig;
|
||||
import net.gensokyoreimagined.nitori.access.IMixinChunkMapAccess;
|
||||
import net.gensokyoreimagined.nitori.access.IMixinChunkMap_TrackedEntityAccess;
|
||||
import net.gensokyoreimagined.nitori.compatibility.PluginCompatibilityRegistry;
|
||||
import net.gensokyoreimagined.nitori.tracker.MultithreadedTracker;
|
||||
import net.minecraft.server.level.ChunkMap;
|
||||
import net.minecraft.server.level.ServerEntity;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
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.Unique;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Mixin(ChunkMap.class)
|
||||
public class ChunkMapMixin implements IMixinChunkMapAccess {
|
||||
|
||||
@Mutable
|
||||
@Shadow @Final public Int2ObjectMap<ChunkMap.TrackedEntity> entityMap;
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Final
|
||||
@Shadow
|
||||
public ServerLevel level;
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Unique
|
||||
private @Nullable MultithreadedTracker gensouHacks$multithreadedTracker;
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Final
|
||||
@Unique
|
||||
private final ConcurrentLinkedQueue<Runnable> gensouHacks$trackerMainThreadTasks = new ConcurrentLinkedQueue<>();
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Unique
|
||||
private boolean gensouHacks$tracking = false;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void reassignEntityTrackers(CallbackInfo ci) {
|
||||
this.entityMap = new Int2ObjectLinkedOpenHashMap<>();
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Override
|
||||
@Unique
|
||||
public void gensouHacks$runOnTrackerMainThread(final Runnable runnable) {
|
||||
if (this.gensouHacks$tracking) {
|
||||
this.gensouHacks$trackerMainThreadTasks.add(runnable);
|
||||
} else {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "processTrackQueue", at = @At("HEAD"), cancellable = true)
|
||||
private void atProcessTrackQueueHead(CallbackInfo callbackInfo) {
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
//TODO: Restore config condition
|
||||
//if (NitoriConfig.enableAsyncEntityTracker) {
|
||||
if (this.gensouHacks$multithreadedTracker == null) {
|
||||
this.gensouHacks$multithreadedTracker = new MultithreadedTracker(this.level.chunkSource.entityTickingChunks, this.gensouHacks$trackerMainThreadTasks);
|
||||
}
|
||||
|
||||
this.gensouHacks$tracking = true;
|
||||
try {
|
||||
this.gensouHacks$multithreadedTracker.tick();
|
||||
} finally {
|
||||
this.gensouHacks$tracking = false;
|
||||
}
|
||||
callbackInfo.cancel();
|
||||
//}
|
||||
// Mirai end
|
||||
}
|
||||
|
||||
@Mixin(ChunkMap.TrackedEntity.class)
|
||||
public static abstract class TrackedEntity implements IMixinChunkMap_TrackedEntityAccess {
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Override
|
||||
@Final
|
||||
@Accessor
|
||||
public abstract Entity getEntity(); // Mirai -> public
|
||||
|
||||
@Final
|
||||
@Mutable
|
||||
@Shadow
|
||||
public Set<ServerPlayerConnection> seenBy;
|
||||
|
||||
@Shadow
|
||||
public abstract void updatePlayer(ServerPlayer player);
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void reassignSeenBy(CallbackInfo ci) {
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
this.seenBy = ReferenceSets.synchronize(new ReferenceOpenHashSet<>()); // Mirai - sync
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Override
|
||||
@Final
|
||||
@Invoker
|
||||
public abstract void callUpdatePlayers(PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates); // Mirai -> public
|
||||
|
||||
@Redirect(method = "updatePlayers(Lcom/destroystokyo/paper/util/misc/PooledLinkedHashSets$PooledObjectLinkedOpenHashSet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ChunkMap$TrackedEntity;updatePlayer(Lnet/minecraft/server/level/ServerPlayer;)V"))
|
||||
private void handleCitizensPluginTracking(ChunkMap.TrackedEntity self, ServerPlayer serverPlayer) {
|
||||
// Nitori - Citizens tracker must run on the main thread to avoid cyclic wait
|
||||
if (PluginCompatibilityRegistry.CITIZENS.shouldRedirectToMainThread(self, serverPlayer)) {
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) serverPlayer.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
|
||||
this.updatePlayer(serverPlayer)
|
||||
);
|
||||
} else {
|
||||
this.updatePlayer(serverPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "removePlayer", at = @At(value = "INVOKE", target = "Lorg/spigotmc/AsyncCatcher;catchOp(Ljava/lang/String;)V"))
|
||||
private void skipSpigotAsyncPlayerTrackerClear(String reason) {} // Mirai - we can remove async too
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "updatePlayer", at = @At(value = "INVOKE", target = "Lorg/spigotmc/AsyncCatcher;catchOp(Ljava/lang/String;)V"))
|
||||
private void skipSpigotAsyncPlayerTrackerUpdate(String reason) {} // Mirai - we can update async
|
||||
|
||||
@Redirect(method = "updatePlayer", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;addPairing(Lnet/minecraft/server/level/ServerPlayer;)V"))
|
||||
private void handleTrainCartsPluginAddPairing(ServerEntity self, ServerPlayer serverPlayer) {
|
||||
if (PluginCompatibilityRegistry.TRAIN_CARTS.shouldRedirectToMainThread((ChunkMap.TrackedEntity) (Object) this)) {
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) serverPlayer.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
|
||||
self.addPairing(serverPlayer)
|
||||
);
|
||||
} else {
|
||||
self.addPairing(serverPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.core;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
|
||||
@Mixin(Direction.class)
|
||||
public class MixinDirection {
|
||||
@Shadow @Final private static Direction[] VALUES;
|
||||
@Shadow @Final private int oppositeIndex;
|
||||
|
||||
/**
|
||||
* @author DoggySazHi
|
||||
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `from3DDataValue`
|
||||
*/
|
||||
@Overwrite
|
||||
public Direction getOpposite() {
|
||||
return VALUES[this.oppositeIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @author DoggySazHi
|
||||
* @reason Implementation of 0005-lithium-fast-util.patch, requires a overwrite to avoid calling `Util.getRandom`
|
||||
*/
|
||||
@Overwrite
|
||||
public static Direction getRandom(RandomSource random) {
|
||||
return VALUES[random.nextInt(VALUES.length)];
|
||||
}
|
||||
}
|
||||
@@ -1,30 +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.core;
|
||||
|
||||
import io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet;
|
||||
import net.gensokyoreimagined.nitori.access.IMixinIteratorSafeOrderedReferenceSetAccess;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(IteratorSafeOrderedReferenceSet.class)
|
||||
public abstract class MixinIteratorSafeOrderedReferenceSet implements IMixinIteratorSafeOrderedReferenceSetAccess {
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
// @Accessor happens to do exactly what is needed, making for nice shorthand
|
||||
@Override
|
||||
@Accessor
|
||||
public abstract int getListSize(); // Mirai - expose listSize
|
||||
}
|
||||
@@ -1,129 +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.core;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import net.gensokyoreimagined.nitori.access.IMixinChunkMapAccess;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBundlePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.server.level.ServerEntity;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mixin(ServerEntity.class)
|
||||
public abstract class MixinServerEntity {
|
||||
@Final
|
||||
@Shadow
|
||||
private Entity entity;
|
||||
|
||||
@Shadow
|
||||
public void sendPairingData(ServerPlayer serverplayer, Consumer<Packet<ClientGamePacketListener>> consumer) {
|
||||
throw new AssertionError("Mixin failed to apply!");
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "removePairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"))
|
||||
private void skipSendForOriginalRemovePairing(ServerGamePacketListenerImpl self, Packet<?> packet) {}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Inject(method = "removePairing", at = @At(value = "TAIL"))
|
||||
private void invokeRemovePairingSendOnMain(ServerPlayer serverplayer, CallbackInfo callbackInfo) {
|
||||
// Mirai start - ensure main thread
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
|
||||
serverplayer.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId()))
|
||||
);
|
||||
// Mirai end
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "addPairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;sendPairingData(Lnet/minecraft/server/level/ServerPlayer;Ljava/util/function/Consumer;)V"))
|
||||
private void skipSendPrepForOriginalAddPairing(ServerEntity self, ServerPlayer serverplayer, Consumer<Packet<ClientGamePacketListener>> consumer) {}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "addPairing", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerGamePacketListenerImpl;send(Lnet/minecraft/network/protocol/Packet;)V"))
|
||||
private void skipSendForOriginalAddPairing(ServerGamePacketListenerImpl self, Packet<?> packet) {}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Inject(method = "addPairing", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerEntity;entity:Lnet/minecraft/world/entity/Entity;", opcode = Opcodes.GETFIELD, shift = At.Shift.BEFORE))
|
||||
private void invokeAddPairingSendOnMain(ServerPlayer serverplayer, CallbackInfo callbackInfo, @Local List<Packet<ClientGamePacketListener>> list) {
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() -> { // Mirai - main thread
|
||||
this.sendPairingData(serverplayer, list::add);
|
||||
serverplayer.connection.send(new ClientboundBundlePacket(list));
|
||||
});
|
||||
}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@SuppressWarnings("EmptyMethod")
|
||||
@Redirect(method = "sendDirtyEntityData", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerEntity;broadcastAndSend(Lnet/minecraft/network/protocol/Packet;)V"))
|
||||
private void skipTrasmitForNonDefault(ServerEntity self, Packet<?> packet) {}
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Inject(method = "sendDirtyEntityData", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "FIELD", target = "Lnet/minecraft/server/level/ServerEntity;entity:Lnet/minecraft/world/entity/Entity;", opcode = Opcodes.GETFIELD, ordinal = 2, shift = At.Shift.BY, by = -4))
|
||||
private void invokeSendForGenericDirtyEntityDataOnMain(CallbackInfo callbackInfo, @Local SynchedEntityData synchedentitydata, @Local List<SynchedEntityData.DataValue<?>> list) {
|
||||
// Mirai start - sync
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() ->
|
||||
this.broadcastAndSend(new ClientboundSetEntityDataPacket(this.entity.getId(), list))
|
||||
);
|
||||
// Mirai end
|
||||
}
|
||||
|
||||
// stubbing of broadcastAndSend in if (this.entity instanceof LivingEntity) handled in skipTrasmitForNonDefault
|
||||
|
||||
// Implementation of 0107-Multithreaded-Tracker.patch
|
||||
@Inject(method = "sendDirtyEntityData", at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V", shift = At.Shift.BY, by = -4))
|
||||
private void invokeSendForLivingDirtyEntityDataOnMain(CallbackInfo callbackInfo, @Local Set<AttributeInstance> set) {
|
||||
// Mirai start - sync
|
||||
final var copy = Lists.newArrayList(set);
|
||||
((IMixinChunkMapAccess) (Object) ((ServerLevel) this.entity.level()).chunkSource.chunkMap).gensouHacks$runOnTrackerMainThread(() -> {
|
||||
// CraftBukkit start - Send scaled max health
|
||||
if (this.entity instanceof ServerPlayer) {
|
||||
((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(copy, false);
|
||||
}
|
||||
// CraftBukkit end
|
||||
this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), copy));
|
||||
});
|
||||
// Mirai end
|
||||
}
|
||||
|
||||
@Shadow
|
||||
private void broadcastAndSend(Packet<?> packet) {
|
||||
throw new AssertionError("Mixin failed to apply!");
|
||||
}
|
||||
}
|
||||
@@ -1,33 +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.core;
|
||||
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(targets = "gg.pufferfish.pufferfish.simd.SIMDDetection")
|
||||
public abstract class MixinSpongeSIMD {
|
||||
@Inject(method = "getJavaVersion", at = @At("RETURN"), cancellable = true)
|
||||
private static void loadPufferfishConfig(CallbackInfoReturnable<Integer> callback) {
|
||||
// We troll the Pufferfish developers by changing the return value of the method
|
||||
// System.out.println("Thought that it was " + callback.getReturnValue() + " for java version");
|
||||
if (callback.getReturnValue() == 21) {
|
||||
System.out.println("Successfully trolled Pufferfish into thinking we're on Java 19!");
|
||||
callback.setReturnValue(19);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +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.core;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.util.Pos;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(WorldGenRegion.class)
|
||||
public class MixinWorldGenRegion {
|
||||
@Shadow @Final private ChunkPos firstPos;
|
||||
@Shadow @Final private int size;
|
||||
|
||||
@Unique
|
||||
private ChunkAccess[] gensouHacks$chunksArr;
|
||||
@Unique
|
||||
private int gensouHacks$minChunkX;
|
||||
@Unique
|
||||
private int gensouHacks$minChunkZ;
|
||||
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void onInit(ServerLevel world, List<ChunkAccess> chunks, ChunkStatus status, int placementRadius, CallbackInfo ci) {
|
||||
this.gensouHacks$minChunkX = this.firstPos.x;
|
||||
this.gensouHacks$minChunkZ = this.firstPos.z;
|
||||
this.gensouHacks$chunksArr = chunks.toArray(new ChunkAccess[0]);
|
||||
}
|
||||
|
||||
@Inject(method = "getChunk(II)Lnet/minecraft/world/level/chunk/ChunkAccess;", at = @At("HEAD"), cancellable = true)
|
||||
public void getChunk(int chunkX, int chunkZ, CallbackInfoReturnable<ChunkAccess> cir) {
|
||||
int x = chunkX - this.gensouHacks$minChunkX;
|
||||
int z = chunkZ - this.gensouHacks$minChunkZ;
|
||||
int w = this.size;
|
||||
|
||||
if (x >= 0 && z >= 0 && x < w && z < w) {
|
||||
cir.setReturnValue(this.gensouHacks$chunksArr[x + z * w]);
|
||||
cir.cancel();
|
||||
} else {
|
||||
throw new NullPointerException("No chunk exists at " + new ChunkPos(chunkX, chunkZ));
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "getBlockState", at = @At("HEAD"), cancellable = true)
|
||||
public void getBlockState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
|
||||
int x = (Pos.ChunkCoord.fromBlockCoord(pos.getX())) - this.gensouHacks$minChunkX;
|
||||
int z = (Pos.ChunkCoord.fromBlockCoord(pos.getZ())) - this.gensouHacks$minChunkZ;
|
||||
int w = this.size;
|
||||
|
||||
if (x >= 0 && z >= 0 && x < w && z < w) {
|
||||
cir.setReturnValue(this.gensouHacks$chunksArr[x + z * w].getBlockState(pos));
|
||||
cir.cancel();
|
||||
} else {
|
||||
throw new NullPointerException("No chunk exists at " + new ChunkPos(pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongList;
|
||||
import net.gensokyoreimagined.nitori.cached_blockpos_iteration.IterateOutwardsCache;
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
@@ -39,7 +39,7 @@ public abstract class MixinEntity {
|
||||
* Implementation of 0065-some-entity-micro-opts.patch
|
||||
*/
|
||||
@Unique
|
||||
public float gensouHacks$getLightLevelDependentMagicValue(BlockPos pos) {
|
||||
public float nitori$getLightLevelDependentMagicValue(BlockPos pos) {
|
||||
return this.level.hasChunkAt(this.getBlockX(), this.getBlockZ()) ? this.level.getLightLevelDependentMagicValue(pos) : 0.0F;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public abstract class MixinEntity {
|
||||
*/
|
||||
@Inject(method = "getLightLevelDependentMagicValue", at = @At("HEAD"), cancellable = true)
|
||||
private void getLightLevelDependentMagicValue(CallbackInfoReturnable<Float> cir) {
|
||||
cir.setReturnValue(this.gensouHacks$getLightLevelDependentMagicValue(new BlockPos(this.getBlockX(), (int) this.getEyeY(), this.getBlockZ())));
|
||||
cir.setReturnValue(this.nitori$getLightLevelDependentMagicValue(new BlockPos(this.getBlockX(), (int) this.getEyeY(), this.getBlockZ())));
|
||||
cir.cancel();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
@@ -79,12 +79,12 @@ public class MixinEntitySectionStorage<T extends EntityAccess> {
|
||||
// are placed somewhere inside the packed long
|
||||
for (int x = j; x <= m; x++) {
|
||||
for (int z = Math.max(l, 0); z <= o; z++) {
|
||||
this.gensouHacks$forEachInColumn(x, k, n, z, consumer);
|
||||
this.nitori$forEachInColumn(x, k, n, z, consumer);
|
||||
}
|
||||
|
||||
int bound = Math.min(-1, o);
|
||||
for (int z = l; z <= bound; z++) {
|
||||
this.gensouHacks$forEachInColumn(x, k, n, z, consumer);
|
||||
this.nitori$forEachInColumn(x, k, n, z, consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,19 +95,19 @@ public class MixinEntitySectionStorage<T extends EntityAccess> {
|
||||
|
||||
// Mirai start - lithium: fast retrieval
|
||||
@Unique
|
||||
private void gensouHacks$forEachInColumn(int x, int k, int n, int z, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
private void nitori$forEachInColumn(int x, int k, int n, int z, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
// y from negative to positive, but y is treated as unsigned
|
||||
for (int y = Math.max(k, 0); y <= n; y++) {
|
||||
this.gensouHacks$consumeSection(SectionPos.asLong(x, y, z), action);
|
||||
this.nitori$consumeSection(SectionPos.asLong(x, y, z), action);
|
||||
}
|
||||
int bound = Math.min(-1, n);
|
||||
for (int y = k; y <= bound; y++) {
|
||||
this.gensouHacks$consumeSection(SectionPos.asLong(x, y, z), action);
|
||||
this.nitori$consumeSection(SectionPos.asLong(x, y, z), action);
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void gensouHacks$consumeSection(long pos, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
private void nitori$consumeSection(long pos, AbortableIterationConsumer<EntitySection<T>> action) {
|
||||
EntitySection<T> entitySection = this.getSection(pos);
|
||||
if (entitySection != null && !entitySection.isEmpty() && entitySection.getStatus().isAccessible()) {
|
||||
action.accept(entitySection);
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.base.Suppliers;
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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 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.CallbackInfoReturnable;
|
||||
|
||||
import space.vectrix.ignite.Blackboard;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Mixin(targets = "io.papermc.paper.pluginremap.ReobfServer")
|
||||
public class MixinReobfServer {
|
||||
@Inject(method = "serverJar", at = @At("HEAD"), cancellable = true)
|
||||
private static void serverJar(CallbackInfoReturnable<Path> cir) {
|
||||
try {
|
||||
var igniteGameJarPath = Blackboard.get(Blackboard.GAME_JAR);
|
||||
System.out.println("Nitori: ReobfServer.serverJar() called" + igniteGameJarPath);
|
||||
|
||||
if (igniteGameJarPath.isPresent()) {
|
||||
System.out.println("Nitori: ReobfServer.serverJar() found Ignite, returning " + igniteGameJarPath.get());
|
||||
cir.setReturnValue(igniteGameJarPath.get());
|
||||
cir.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// This is a fallback in case the game jar path is not set for some reason
|
||||
var path = Path.of(MixinReobfServer.class.getProtectionDomain().getCodeSource().getLocation().toURI());
|
||||
System.out.println("Nitori: ReobfServer.serverJar() tried loading, returning " + path);
|
||||
cir.setReturnValue(path);
|
||||
cir.cancel();
|
||||
} catch (final URISyntaxException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.alloc;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
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.Unique;
|
||||
|
||||
@Mixin(ResourceLocation.class)
|
||||
public class MixinIdentifier {
|
||||
|
||||
@Shadow @Final
|
||||
private String namespace;
|
||||
|
||||
@Shadow @Final
|
||||
private String path;
|
||||
|
||||
@Unique
|
||||
private String nitori$cachedString = null;
|
||||
|
||||
/**
|
||||
* @author ishland
|
||||
* @reason cache toString
|
||||
*/
|
||||
@Overwrite
|
||||
public String toString() {
|
||||
if (this.nitori$cachedString != null) return this.nitori$cachedString;
|
||||
final String s = this.namespace + ":" + this.path;
|
||||
this.nitori$cachedString = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.alloc.biome_temprature_leak;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatLinkedOpenHashMap;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(Biome.class)
|
||||
public abstract class Biome_threadLocalMixin {
|
||||
|
||||
/*
|
||||
* Not only do I absolutely hate this garbage cache, cause it barely works at all.
|
||||
* HOW did nobody at mojang notice that this thread local was not static, how on earth is a non-static private
|
||||
* value suppose to run on multiple threads at the same time.
|
||||
* So now its static so that it actually works like its suppose to, and you don't need to declare a billion of them
|
||||
*
|
||||
* This is a significant performance boost & reduces a lot of memory usage
|
||||
*/
|
||||
|
||||
private static ThreadLocal<Long2FloatLinkedOpenHashMap> memoryLeakFix$betterTempCache;
|
||||
|
||||
@WrapOperation(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Ljava/lang/ThreadLocal;withInitial(Ljava/util/function/Supplier;)Ljava/lang/ThreadLocal;"
|
||||
)
|
||||
)
|
||||
private ThreadLocal<Long2FloatLinkedOpenHashMap> memoryLeakFix$useStaticThreadLocal(Supplier<?> supplier, Operation<ThreadLocal<Long2FloatLinkedOpenHashMap>> original) {
|
||||
if (memoryLeakFix$betterTempCache == null) {
|
||||
memoryLeakFix$betterTempCache = original.call(supplier);
|
||||
}
|
||||
return memoryLeakFix$betterTempCache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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.alloc.blockstate;
|
||||
|
||||
import com.google.common.collect.Table;
|
||||
import net.gensokyoreimagined.nitori.common.state.FastImmutableTable;
|
||||
import net.gensokyoreimagined.nitori.common.state.StatePropertyTableCache;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.StateHolder;
|
||||
import net.minecraft.world.level.block.state.properties.Property;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(StateHolder.class)
|
||||
public class StateMixin<O, S> {
|
||||
@Shadow
|
||||
private Table<Property<?>, Comparable<?>, S> neighbours;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
protected O owner;
|
||||
|
||||
@Inject(method = "populateNeighbours", at = @At("RETURN"))
|
||||
private void postCreateWithTable(Map<Map<Property<?>, Comparable<?>>, S> states, CallbackInfo ci) {
|
||||
if (this.owner instanceof Block || this.owner instanceof Fluid) {
|
||||
this.neighbours = new FastImmutableTable<Property<?>, Comparable<?>, S>(this.neighbours, StatePropertyTableCache.getTableCache(this.owner));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.alloc.composter;
|
||||
|
||||
import net.gensokyoreimagined.nitori.common.util.ArrayConstants;
|
||||
import net.minecraft.world.WorldlyContainer;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Overwrite;
|
||||
|
||||
public class ComposterMixin {
|
||||
|
||||
@Mixin(targets = "net.minecraft.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,6 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.alloc;
|
||||
|
||||
/*
|
||||
/nbt/NbtCompoundMixin is not needed
|
||||
|
||||
*/
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin.cached_hashcode;
|
||||
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
@@ -22,16 +22,8 @@ import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/*
|
||||
* Originally from CaffeineMC, licensed under GNU Lesser General Public License v3.0
|
||||
* See https://github.com/CaffeineMC/lithium-fabric for more information/sources
|
||||
*/
|
||||
|
||||
@Mixin(Block.BlockStatePairKey.class)
|
||||
public class MixinBlock {
|
||||
@Unique
|
||||
private int gensouHacks$hash;
|
||||
|
||||
public class BlockNeighborGroupMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private BlockState first;
|
||||
@@ -44,20 +36,27 @@ public class MixinBlock {
|
||||
@Final
|
||||
private Direction direction;
|
||||
|
||||
@Unique
|
||||
private int nitori$hash;
|
||||
|
||||
/**
|
||||
* @reason Initialize the object's hashcode and cache it
|
||||
*/
|
||||
@Inject(method = "<init>", at = @At("RETURN"))
|
||||
private void init(CallbackInfo info) {
|
||||
int hash = first.hashCode();
|
||||
hash = 31 * hash + second.hashCode();
|
||||
hash = 31 * hash + direction.hashCode();
|
||||
this.gensouHacks$hash = hash;
|
||||
private void generateHash(BlockState blockState_1, BlockState blockState_2, Direction direction_1, CallbackInfo ci) {
|
||||
int hash = this.first.hashCode();
|
||||
hash = 31 * hash + this.second.hashCode();
|
||||
hash = 31 * hash + this.direction.hashCode();
|
||||
|
||||
this.nitori$hash = hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author DoggySazHi
|
||||
* @reason Implementation of 0045-lithium-cached-hashcode.patch
|
||||
* @reason Uses the cached hashcode
|
||||
* @author JellySquid
|
||||
*/
|
||||
@Overwrite
|
||||
@Overwrite(remap = false)
|
||||
public int hashCode() {
|
||||
return this.gensouHacks$hash;
|
||||
return this.nitori$hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.chunk.entity_class_groups;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
|
||||
import net.gensokyoreimagined.nitori.common.entity.EntityClassGroup;
|
||||
import net.gensokyoreimagined.nitori.common.world.chunk.ClassGroupFilterableList;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.util.ClassInstanceMultiMap;
|
||||
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.ModifyVariable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Patches {@link ClassInstanceMultiMap} to allow grouping entities by arbitrary groups of classes instead of one class only.
|
||||
*/
|
||||
@Mixin(ClassInstanceMultiMap.class)
|
||||
public abstract class TypeFilterableListMixin<T> implements ClassGroupFilterableList<T> {
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private List<T> allInstances;
|
||||
|
||||
private final Reference2ReferenceArrayMap<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> nitori$entitiesByGroup =
|
||||
new Reference2ReferenceArrayMap<>();
|
||||
|
||||
/**
|
||||
* Update our collections
|
||||
*/
|
||||
@ModifyVariable(method = "add(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true)
|
||||
public T add(T entity) {
|
||||
for (Map.Entry<EntityClassGroup, ReferenceLinkedOpenHashSet<T>> entityGroupAndSet : this.nitori$entitiesByGroup.entrySet()) {
|
||||
EntityClassGroup entityGroup = entityGroupAndSet.getKey();
|
||||
if (entityGroup.contains(((Entity) entity).getClass())) {
|
||||
entityGroupAndSet.getValue().add((entity));
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update our collections
|
||||
*/
|
||||
@ModifyVariable(method = "remove(Ljava/lang/Object;)Z", at = @At("HEAD"), argsOnly = true)
|
||||
public Object remove(Object o) {
|
||||
for (ReferenceLinkedOpenHashSet<T> entitySet : this.nitori$entitiesByGroup.values()) {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
entitySet.remove(o);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entities of a class group
|
||||
*/
|
||||
public Collection<T> lithium$getAllOfGroupType(EntityClassGroup type) {
|
||||
Collection<T> collection = this.nitori$entitiesByGroup.get(type);
|
||||
|
||||
if (collection == null) {
|
||||
collection = this.nitori$createAllOfGroupType(type);
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start grouping by a new class group
|
||||
*/
|
||||
private Collection<T> nitori$createAllOfGroupType(EntityClassGroup type) {
|
||||
ReferenceLinkedOpenHashSet<T> allOfType = new ReferenceLinkedOpenHashSet<>();
|
||||
|
||||
for (T entity : this.allInstances) {
|
||||
if (type.contains(entity.getClass())) {
|
||||
allOfType.add(entity);
|
||||
}
|
||||
}
|
||||
this.nitori$entitiesByGroup.put(type, allOfType);
|
||||
|
||||
return allOfType;
|
||||
}
|
||||
}
|
||||
@@ -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,53 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.brain;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import net.minecraft.world.entity.ai.Brain;
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
|
||||
import net.minecraft.world.entity.ai.memory.MemoryStatus;
|
||||
import net.minecraft.world.entity.schedule.Activity;
|
||||
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.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Mixin(Brain.class)
|
||||
public class BrainMixin {
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<?, ?> memories;
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Map<?, ?> sensors;
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
@Mutable
|
||||
private Map<Activity, Set<Pair<MemoryModuleType<?>, MemoryStatus>>> activityRequirements;
|
||||
|
||||
@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);
|
||||
this.activityRequirements = new Object2ObjectOpenHashMap<>(this.activityRequirements);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,44 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.fluid_submersion;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArraySet;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.tags.TagKey;
|
||||
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.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public class EntityMixin {
|
||||
|
||||
@Mutable
|
||||
@Shadow
|
||||
@Final
|
||||
private Set<TagKey<Fluid>> fluidOnEyes;
|
||||
|
||||
@Inject(
|
||||
method = "<init>",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void useReferenceArraySet(CallbackInfo ci) {
|
||||
this.fluidOnEyes = new ReferenceArraySet<>(this.fluidOnEyes);
|
||||
}
|
||||
|
||||
@Redirect(
|
||||
method = "updateFluidOnEyes",
|
||||
at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V"),
|
||||
require = 0
|
||||
)
|
||||
private void clearIfNotEmpty(Set<?> set) {
|
||||
if (!set.isEmpty()) {
|
||||
set.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
package net.gensokyoreimagined.nitori.core;
|
||||
package net.gensokyoreimagined.nitori.mixin.collections.gamerules;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
@@ -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,207 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.entity.collisions.movement;
|
||||
|
||||
//import com.llamalad7.mixinextras.sugar.Share;
|
||||
//import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
|
||||
//import net.gensokyoreimagined.nitori.common.entity.LithiumEntityCollisions;
|
||||
//import net.gensokyoreimagined.nitori.common.entity.movement.ChunkAwareBlockCollisionSweeper;
|
||||
//import net.gensokyoreimagined.nitori.common.util.collections.LazyList;
|
||||
//import net.minecraft.entity.Entity;
|
||||
//import net.minecraft.util.math.Box;
|
||||
//import net.minecraft.util.math.Direction;
|
||||
//import net.minecraft.util.math.Vec3d;
|
||||
//import net.minecraft.util.shape.VoxelShape;
|
||||
//import net.minecraft.util.shape.VoxelShapes;
|
||||
//import net.minecraft.world.World;
|
||||
//import org.jetbrains.annotations.Nullable;
|
||||
//import org.spongepowered.asm.mixin.Mixin;
|
||||
//import org.spongepowered.asm.mixin.Overwrite;
|
||||
//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.ModifyVariable;
|
||||
//import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
//
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.List;
|
||||
//
|
||||
//@Mixin(Entity.class)
|
||||
//public abstract class EntityMixin {
|
||||
// @Shadow
|
||||
// public abstract World getWorld();
|
||||
//
|
||||
// @Shadow
|
||||
// public abstract Box getBoundingBox();
|
||||
//
|
||||
// @Redirect(
|
||||
// method = "adjustMovementForCollisions(Lnet/minecraft/util/math/Vec3d;)Lnet/minecraft/util/math/Vec3d;",
|
||||
// at = @At(
|
||||
// value = "INVOKE",
|
||||
// target = "Lnet/minecraft/world/World;getEntityCollisions(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/Box;)Ljava/util/List;"
|
||||
// )
|
||||
// )
|
||||
// private List<VoxelShape> postponeGetEntities(World world, Entity entity, Box box, @Share("requireAddEntities") LocalBooleanRef requireAddEntities) {
|
||||
// requireAddEntities.set(true);
|
||||
// return new ArrayList<>();
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @Redirect(
|
||||
// method = "adjustMovementForCollisions(Lnet/minecraft/util/math/Vec3d;)Lnet/minecraft/util/math/Vec3d;",
|
||||
// at = @At(
|
||||
// value = "INVOKE",
|
||||
// target = "Lnet/minecraft/entity/Entity;adjustMovementForCollisions(Lnet/minecraft/entity/Entity;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Box;Lnet/minecraft/world/World;Ljava/util/List;)Lnet/minecraft/util/math/Vec3d;"
|
||||
// )
|
||||
// )
|
||||
// private Vec3d collideMovementWithPostponedGetEntities(@Nullable Entity entity, Vec3d movement, Box entityBoundingBox, World world, List<VoxelShape> entityCollisions, @Share("requireAddEntities") LocalBooleanRef requireAddEntities) {
|
||||
// return lithium$CollideMovement(entity, movement, entityBoundingBox, world, entityCollisions, requireAddEntities);
|
||||
// }
|
||||
//
|
||||
// @ModifyVariable(
|
||||
// method = "adjustMovementForCollisions(Lnet/minecraft/util/math/Vec3d;)Lnet/minecraft/util/math/Vec3d;",
|
||||
// at = @At(
|
||||
// value = "INVOKE", shift = At.Shift.BEFORE,
|
||||
// target = "Lnet/minecraft/entity/Entity;findCollisionsForMovement(Lnet/minecraft/entity/Entity;Lnet/minecraft/world/World;Ljava/util/List;Lnet/minecraft/util/math/Box;)Ljava/util/List;")
|
||||
// )
|
||||
// private List<VoxelShape> collectEntities(List<VoxelShape> entityCollisions, @Share("requireAddEntities") LocalBooleanRef requireAddEntities) {
|
||||
// if (requireAddEntities.get()) {
|
||||
// requireAddEntities.set(false);
|
||||
// ArrayList<VoxelShape> collisions = entityCollisions instanceof ArrayList<VoxelShape> ? (ArrayList<VoxelShape>) entityCollisions : new ArrayList<>(entityCollisions);
|
||||
// LithiumEntityCollisions.appendEntityCollisions(collisions, this.getWorld(), (Entity) (Object) this, this.getBoundingBox());
|
||||
// return collisions;
|
||||
// }
|
||||
// return entityCollisions;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * @author 2No2Name
|
||||
// * @reason Replace with optimized implementation
|
||||
// */
|
||||
// @Overwrite
|
||||
// public static Vec3d adjustMovementForCollisions(@Nullable Entity entity, Vec3d movement, Box entityBoundingBox, World world, List<VoxelShape> entityCollisions) {
|
||||
// return lithium$CollideMovement(entity, movement, entityBoundingBox, world, entityCollisions, null);
|
||||
// }
|
||||
// @Unique
|
||||
// private static Vec3d lithium$CollideMovement(@Nullable Entity entity, Vec3d movement, Box entityBoundingBox, World world, List<VoxelShape> entityCollisions, LocalBooleanRef requireAddEntities) {
|
||||
// //vanilla order: entities, world border, blocks.
|
||||
// // The most important ordering constraint is that the last collision is last, since the result is not clipped to 0 when it is <1e-7.
|
||||
// // Other reordering of collisions does not seem to matter.
|
||||
// double movementX = movement.x;
|
||||
// double movementY = movement.y;
|
||||
// double movementZ = movement.z;
|
||||
// boolean isSingleAxisMovement = (movementX == 0D ? 0 : 1) + (movementY == 0D ? 0 : 1) + (movementZ == 0D ? 0 : 1) == 1;
|
||||
//
|
||||
// if (movementY < 0D) {
|
||||
// //Downwards / gravity optimization: Check supporting block or directly below center of entity first
|
||||
// VoxelShape voxelShape = LithiumEntityCollisions.getSupportingCollisionForEntity(world, entity, entityBoundingBox);
|
||||
// if (voxelShape != null) {
|
||||
// double v = voxelShape.calculateMaxDistance(Direction.Axis.Y, entityBoundingBox, movementY);
|
||||
// if (v == 0) {
|
||||
// if (isSingleAxisMovement) {
|
||||
// //Y was the only movement axis, movement completely cancelled<
|
||||
// return Vec3d.ZERO;
|
||||
// }
|
||||
// movementY = 0D;
|
||||
// isSingleAxisMovement = (movementX == 0D ? 0 : 1) + (movementZ == 0D ? 0 : 1) == 1;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Box movementSpace;
|
||||
// if (isSingleAxisMovement) {
|
||||
// movementSpace = LithiumEntityCollisions.getSmallerBoxForSingleAxisMovement(movement, entityBoundingBox, movementY, movementX, movementZ);
|
||||
// } else {
|
||||
// movementSpace = entityBoundingBox.stretch(movement);
|
||||
// }
|
||||
//
|
||||
// boolean shouldAddEntities = requireAddEntities != null && requireAddEntities.get();
|
||||
// boolean shouldAddWorldBorder = true;
|
||||
// // For 1-e7 margin behavior correctness, the last block collision must be last of all collisions
|
||||
// boolean shouldAddLastBlock = true;
|
||||
// ChunkAwareBlockCollisionSweeper blockCollisionSweeper = new ChunkAwareBlockCollisionSweeper(world, entity, movementSpace, true);
|
||||
//
|
||||
// LazyList<VoxelShape> blockCollisions = new LazyList<>(new ArrayList<>(), blockCollisionSweeper);
|
||||
// ArrayList<VoxelShape> worldBorderAndLastBlockCollision = new ArrayList<>(2);
|
||||
//
|
||||
// if (movementY != 0.0) {
|
||||
// movementY = VoxelShapes.calculateMaxOffset(Direction.Axis.Y, entityBoundingBox, blockCollisions, movementY);
|
||||
// if (movementY != 0.0) {
|
||||
// shouldAddEntities = LithiumEntityCollisions.addEntityCollisionsIfRequired(shouldAddEntities, entity, world, entityCollisions, movementSpace);
|
||||
// //noinspection ConstantValue
|
||||
// shouldAddWorldBorder = LithiumEntityCollisions.addWorldBorderCollisionIfRequired(shouldAddWorldBorder, entity, worldBorderAndLastBlockCollision, movementSpace);
|
||||
// //noinspection ConstantValue
|
||||
// shouldAddLastBlock = LithiumEntityCollisions.addLastBlockCollisionIfRequired(shouldAddLastBlock, blockCollisionSweeper, worldBorderAndLastBlockCollision);
|
||||
// if (!entityCollisions.isEmpty()) {
|
||||
// movementY = VoxelShapes.calculateMaxOffset(Direction.Axis.Y, entityBoundingBox, entityCollisions, movementY);
|
||||
// }
|
||||
// if (!worldBorderAndLastBlockCollision.isEmpty()) {
|
||||
// movementY = VoxelShapes.calculateMaxOffset(Direction.Axis.Y, entityBoundingBox, worldBorderAndLastBlockCollision, movementY);
|
||||
// }
|
||||
//
|
||||
// if (movementY != 0.0) {
|
||||
// entityBoundingBox = entityBoundingBox.offset(0.0, movementY, 0.0);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// boolean zMovementBiggerThanXMovement = Math.abs(movementX) < Math.abs(movementZ);
|
||||
// if (zMovementBiggerThanXMovement) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, blockCollisions, movementZ);
|
||||
// if (movementZ != 0.0) {
|
||||
// //noinspection DuplicatedCode
|
||||
// shouldAddEntities = LithiumEntityCollisions.addEntityCollisionsIfRequired(shouldAddEntities, entity, world, entityCollisions, movementSpace);
|
||||
// shouldAddWorldBorder = LithiumEntityCollisions.addWorldBorderCollisionIfRequired(shouldAddWorldBorder, entity, worldBorderAndLastBlockCollision, movementSpace);
|
||||
// shouldAddLastBlock = LithiumEntityCollisions.addLastBlockCollisionIfRequired(shouldAddLastBlock, blockCollisionSweeper, worldBorderAndLastBlockCollision);
|
||||
// if (!entityCollisions.isEmpty()) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, entityCollisions, movementZ);
|
||||
// }
|
||||
// if (!worldBorderAndLastBlockCollision.isEmpty()) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, worldBorderAndLastBlockCollision, movementZ);
|
||||
// }
|
||||
//
|
||||
// if (movementZ != 0.0) {
|
||||
// entityBoundingBox = entityBoundingBox.offset(0.0, 0.0, movementZ);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (movementX != 0.0) {
|
||||
// movementX = VoxelShapes.calculateMaxOffset(Direction.Axis.X, entityBoundingBox, blockCollisions, movementX);
|
||||
// if (movementX != 0.0) {
|
||||
// shouldAddEntities = LithiumEntityCollisions.addEntityCollisionsIfRequired(shouldAddEntities, entity, world, entityCollisions, movementSpace);
|
||||
// shouldAddWorldBorder = LithiumEntityCollisions.addWorldBorderCollisionIfRequired(shouldAddWorldBorder, entity, worldBorderAndLastBlockCollision, movementSpace);
|
||||
// shouldAddLastBlock = LithiumEntityCollisions.addLastBlockCollisionIfRequired(shouldAddLastBlock, blockCollisionSweeper, worldBorderAndLastBlockCollision);
|
||||
// if (!entityCollisions.isEmpty()) {
|
||||
// movementX = VoxelShapes.calculateMaxOffset(Direction.Axis.X, entityBoundingBox, entityCollisions, movementX);
|
||||
// }
|
||||
// if (!worldBorderAndLastBlockCollision.isEmpty()) {
|
||||
// movementX = VoxelShapes.calculateMaxOffset(Direction.Axis.X, entityBoundingBox, worldBorderAndLastBlockCollision, movementX);
|
||||
// }
|
||||
//
|
||||
// if (movementX != 0.0) {
|
||||
// entityBoundingBox = entityBoundingBox.offset(movementX, 0.0, 0.0);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if (!zMovementBiggerThanXMovement && movementZ != 0.0) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, blockCollisions, movementZ);
|
||||
// if (movementZ != 0.0) {
|
||||
// //noinspection DuplicatedCode
|
||||
// shouldAddEntities = LithiumEntityCollisions.addEntityCollisionsIfRequired(shouldAddEntities, entity, world, entityCollisions, movementSpace);
|
||||
// //noinspection UnusedAssignment
|
||||
// shouldAddWorldBorder = LithiumEntityCollisions.addWorldBorderCollisionIfRequired(shouldAddWorldBorder, entity, worldBorderAndLastBlockCollision, movementSpace);
|
||||
// //noinspection UnusedAssignment
|
||||
// shouldAddLastBlock = LithiumEntityCollisions.addLastBlockCollisionIfRequired(shouldAddLastBlock, blockCollisionSweeper, worldBorderAndLastBlockCollision);
|
||||
// if (!entityCollisions.isEmpty()) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, entityCollisions, movementZ);
|
||||
// }
|
||||
// if (!worldBorderAndLastBlockCollision.isEmpty()) {
|
||||
// movementZ = VoxelShapes.calculateMaxOffset(Direction.Axis.Z, entityBoundingBox, worldBorderAndLastBlockCollision, movementZ);
|
||||
// }
|
||||
// //No need to offset box here, as it is the last axis
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (requireAddEntities != null && !shouldAddEntities) {
|
||||
// requireAddEntities.set(false);
|
||||
// }
|
||||
// return new Vec3d(movementX, movementY, movementZ);
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.entity.collisions.movement;
|
||||
|
||||
/*
|
||||
TODO: https://github.com/CaffeineMC/lithium-fabric/blob/3bdbb553b47165bdbd8c3ca0807bb374a19ce04a/src/main/java/me/jellysquid/mods/lithium/mixin/entity/collisions/movement/EntityMixin.java
|
||||
*/
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.gensokyoreimagined.nitori.mixin.entity.collisions.unpushable_cramming;
|
||||
|
||||
import com.google.common.base.Predicates;
|
||||
import net.gensokyoreimagined.nitori.common.entity.pushable.EntityPushablePredicate;
|
||||
import net.gensokyoreimagined.nitori.common.world.WorldHelper;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.vehicle.Boat;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.entity.EntitySectionStorage;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(Boat.class)
|
||||
public class BoatEntityMixin {
|
||||
@Redirect(
|
||||
method = "tick",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/level/Level;getEntities(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/phys/AABB;Ljava/util/function/Predicate;)Ljava/util/List;"
|
||||
)
|
||||
)
|
||||
private List<Entity> getOtherPushableEntities(Level world, @Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
|
||||
//noinspection Guava
|
||||
if (predicate == Predicates.alwaysFalse()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (predicate instanceof EntityPushablePredicate<?> entityPushablePredicate) {
|
||||
EntitySectionStorage<Entity> cache = WorldHelper.getEntityCacheOrNull(world);
|
||||
if (cache != null) {
|
||||
//noinspection unchecked
|
||||
return WorldHelper.getPushableEntities(world, cache, except, box, (EntityPushablePredicate<? super Entity>) entityPushablePredicate);
|
||||
}
|
||||
}
|
||||
return world.getEntities(except, box, predicate);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user