mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Merged 1.21.1 mod targets
- Merge feature/modded-versions into the modded fix integration - Take loader pins from gradle/mod-targets.json for every target - Pin the 26.3 NeoForge target to 26.3.0.83
This commit is contained in:
commit
b8aa00b70e
58 files changed
+3279
-133
No files matched your search
@@ -168,6 +168,12 @@ public final class ModdedEngineBootstrap {
|
||||
ModdedStartup.prepareDimensionRestore(server);
|
||||
}
|
||||
|
||||
public static void levelsReady(NativeModdedServer server) {
|
||||
server.refreshWorlds();
|
||||
bindWorldGenerators(server);
|
||||
ModdedStartup.reinjectPersistentDimensions(server);
|
||||
}
|
||||
|
||||
public static void serverStarted(NativeModdedServer server) {
|
||||
// Prime the off-thread level snapshot before anything can read it; the per-tick refresh in
|
||||
// ModdedScheduler.tick has not run yet at this point.
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ public final class ModdedLifecycleCallbacks {
|
||||
return new NativeModdedCallbacks(
|
||||
ModdedEngineBootstrap::serverAboutToStart,
|
||||
ModdedEngineBootstrap::start,
|
||||
ModdedEngineBootstrap::levelsReady,
|
||||
ModdedEngineBootstrap::serverStarted,
|
||||
ModdedEngineBootstrap::stop,
|
||||
ModdedEngineBootstrap::stopped,
|
||||
|
||||
@@ -45,6 +45,9 @@ public final class ModdedMixinAudit {
|
||||
private static final AtomicBoolean AUDITED = new AtomicBoolean(false);
|
||||
|
||||
static final List<ExpectedMixin> EXPECTED = List.of(
|
||||
new ExpectedMixin(ENTITY_CONFIG, "ServerLevelsReadyMixin",
|
||||
NativeMixinTarget.SERVER, "volmlib$levelsReady",
|
||||
false, NativeMixinFlags::levelsReadyRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "RegistryClientNamesMixin",
|
||||
NativeMixinTarget.REGISTRY_PACKET, "volmlib$clientRegistryNames",
|
||||
false, NativeMixinFlags::registryClientNamesRan),
|
||||
|
||||
@@ -120,7 +120,6 @@ public final class ModdedStartup {
|
||||
}
|
||||
ModdedForcedDatapack.verifyInjected();
|
||||
ModdedMixinAudit.runOnce();
|
||||
reinjectPersistentDimensions(server);
|
||||
|
||||
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
|
||||
if (scheduler == null) {
|
||||
|
||||
+64
-31
@@ -114,15 +114,9 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
if (!sync) {
|
||||
try {
|
||||
semaphore.tryAcquire(maxInFlight, 5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
ModdedIrisLog.info("Iris modded pregen done: dim={} completed={} peakInFlight={} finalLimit={}",
|
||||
runtime.worldIdentity(), completed.get(), inFlightPeak.get(), adaptiveLimit.get());
|
||||
awaitPendingChunks();
|
||||
ModdedIrisLog.info("Iris modded pregen closing: dim={} completed={} pending={} peakInFlight={} finalLimit={}",
|
||||
runtime.worldIdentity(), completed.get(), inFlight.get(), inFlightPeak.get(), adaptiveLimit.get());
|
||||
try {
|
||||
engine.syncGenerationHistory();
|
||||
} catch (RuntimeException e) {
|
||||
@@ -180,6 +174,39 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
|| queuedFinalSave.get() != null;
|
||||
}
|
||||
|
||||
private void awaitPendingChunks() {
|
||||
if (sync || runtime.isServerThread()) {
|
||||
return;
|
||||
}
|
||||
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(timeoutSeconds)
|
||||
+ TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
||||
boolean interrupted = false;
|
||||
try {
|
||||
while (inFlight.get() > 0) {
|
||||
if (finalSaveOnServerThread.get() || !runtime.isRunning()) {
|
||||
return;
|
||||
}
|
||||
if (System.nanoTime() >= deadline) {
|
||||
throw new IllegalStateException("Iris pregen chunk callbacks did not finish for "
|
||||
+ runtime.worldIdentity() + ": " + inFlight.get() + " pending");
|
||||
}
|
||||
try {
|
||||
synchronized (permitMonitor) {
|
||||
if (inFlight.get() > 0) {
|
||||
permitMonitor.wait(FINAL_SAVE_POLL_MILLIS);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ignored) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLevel(boolean wait) {
|
||||
if (wait) {
|
||||
saveFinalLevel();
|
||||
@@ -237,30 +264,36 @@ public final class ModdedPregenMethod implements PregeneratorMethod {
|
||||
|
||||
private void awaitFinalSave(FinalSaveRequest request) {
|
||||
long deadline = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(FINAL_SAVE_TIMEOUT_MILLIS);
|
||||
while (true) {
|
||||
if (deferFinalSaveIfRequested()) {
|
||||
return;
|
||||
boolean interrupted = false;
|
||||
try {
|
||||
while (true) {
|
||||
if (deferFinalSaveIfRequested()) {
|
||||
return;
|
||||
}
|
||||
long remainingNanos = deadline - System.nanoTime();
|
||||
if (remainingNanos <= 0L) {
|
||||
ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", runtime.worldIdentity());
|
||||
return;
|
||||
}
|
||||
long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS,
|
||||
TimeUnit.NANOSECONDS.toMillis(remainingNanos)));
|
||||
try {
|
||||
request.completion().get(waitMillis, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
} catch (InterruptedException ignored) {
|
||||
interrupted = true;
|
||||
} catch (TimeoutException e) {
|
||||
continue;
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
ModdedIrisLog.error("Iris pregen level save failed for {}", runtime.worldIdentity(), cause);
|
||||
throw new IllegalStateException("Iris pregen level save failed for "
|
||||
+ runtime.worldIdentity(), cause);
|
||||
}
|
||||
}
|
||||
long remainingNanos = deadline - System.nanoTime();
|
||||
if (remainingNanos <= 0L) {
|
||||
ModdedIrisLog.warn("Iris pregen level save did not complete in time for {}", runtime.worldIdentity());
|
||||
return;
|
||||
}
|
||||
long waitMillis = Math.max(1L, Math.min(FINAL_SAVE_POLL_MILLIS,
|
||||
TimeUnit.NANOSECONDS.toMillis(remainingNanos)));
|
||||
try {
|
||||
request.completion().get(waitMillis, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
} finally {
|
||||
if (interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
} catch (TimeoutException e) {
|
||||
continue;
|
||||
} catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
ModdedIrisLog.error("Iris pregen level save failed for {}", runtime.worldIdentity(), cause);
|
||||
throw new IllegalStateException("Iris pregen level save failed for "
|
||||
+ runtime.worldIdentity(), cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedStartupLifecycleTest {
|
||||
@After
|
||||
public void resetStartup() {
|
||||
ModdedStartup.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoresAfterWorldsLoadWithoutRepeatingDuringServerTicks() {
|
||||
NativeModdedServer server = mock(NativeModdedServer.class);
|
||||
AtomicBoolean worldsRefreshed = new AtomicBoolean();
|
||||
when(server.refreshWorlds()).thenAnswer(invocation -> {
|
||||
worldsRefreshed.set(true);
|
||||
return Thread.currentThread();
|
||||
});
|
||||
when(server.hasPlayerList()).thenReturn(true);
|
||||
ModdedDimensionRegistryStore.PersistentDimension dimension =
|
||||
new ModdedDimensionRegistryStore.PersistentDimension("iris:moon", "overworld", "overworld", 1337L);
|
||||
|
||||
try (MockedStatic<ModdedStartup> startup = mockStatic(ModdedStartup.class, CALLS_REAL_METHODS);
|
||||
MockedStatic<ModdedDimensionRegistryStore> store = mockStatic(ModdedDimensionRegistryStore.class);
|
||||
MockedStatic<ModdedDimensionManager> manager = mockStatic(ModdedDimensionManager.class);
|
||||
MockedStatic<ModdedForcedDatapack> datapack = mockStatic(ModdedForcedDatapack.class);
|
||||
MockedStatic<ModdedMixinAudit> audit = mockStatic(ModdedMixinAudit.class);
|
||||
MockedStatic<ModdedIrisLog> log = mockStatic(ModdedIrisLog.class)) {
|
||||
startup.when(ModdedStartup::prepareForStartup).thenAnswer(invocation -> null);
|
||||
store.when(() -> ModdedDimensionRegistryStore.loadForStartup(server)).thenReturn(List.of(dimension));
|
||||
manager.when(() -> ModdedDimensionManager.restorePersistent(server, "iris:moon", "overworld", "overworld", 1337L))
|
||||
.thenAnswer(invocation -> {
|
||||
assertTrue(worldsRefreshed.get());
|
||||
return null;
|
||||
});
|
||||
|
||||
ModdedLifecycleCallbacks.create().levelsReady().accept(server);
|
||||
|
||||
manager.verify(() -> ModdedDimensionManager.restorePersistent(server, "iris:moon", "overworld", "overworld", 1337L));
|
||||
ModdedStartup.runOnce(server);
|
||||
ModdedStartup.runOnce(server);
|
||||
store.verify(() -> ModdedDimensionRegistryStore.loadForStartup(server), times(1));
|
||||
manager.verify(() -> ModdedDimensionManager.restorePersistent(server, "iris:moon", "overworld", "overworld", 1337L), times(1));
|
||||
datapack.verify(ModdedForcedDatapack::verifyInjected, times(1));
|
||||
audit.verify(ModdedMixinAudit::runOnce, times(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
-1
@@ -3,14 +3,25 @@ package art.arcane.iris.modded.command;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.modded.ModdedGenPool;
|
||||
import art.arcane.iris.world.pregen.MantleHeapPressure;
|
||||
import art.arcane.iris.world.pregen.PregenListener;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativePregenRuntime;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativePregenRuntime.ChunkLoad;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -20,6 +31,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
@@ -31,6 +43,7 @@ import static org.mockito.Mockito.when;
|
||||
public class ModdedPregenMethodLifecycleTest {
|
||||
private MockedStatic<IrisSettings> settings;
|
||||
private NativePregenRuntime runtime;
|
||||
private Engine engine;
|
||||
private ModdedPregenMethod method;
|
||||
|
||||
@Before
|
||||
@@ -40,7 +53,7 @@ public class ModdedPregenMethodLifecycleTest {
|
||||
settings.when(IrisSettings::get).thenReturn(configured);
|
||||
runtime = mock(NativePregenRuntime.class);
|
||||
when(runtime.worldIdentity()).thenReturn("example:terrain");
|
||||
Engine engine = mock(Engine.class);
|
||||
engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
when(engine.getHeight()).thenReturn(384);
|
||||
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, true));
|
||||
}
|
||||
@@ -82,6 +95,131 @@ public class ModdedPregenMethodLifecycleTest {
|
||||
verify(runtime, never()).save();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void finalSaveWaitsForEveryCallbackBeyondFiveSeconds() throws Exception {
|
||||
CompletableFuture<ChunkLoad> load = new CompletableFuture<>();
|
||||
CompletableFuture<ChunkLoad> failedLoad = new CompletableFuture<>();
|
||||
PregenListener listener = mock(PregenListener.class);
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
when(runtime.isRunning()).thenReturn(true);
|
||||
when(runtime.loadChunk(2, 3)).thenReturn(load);
|
||||
when(runtime.loadChunk(4, 5)).thenReturn(failedLoad);
|
||||
doAnswer(call -> {
|
||||
call.getArgument(0, Runnable.class).run();
|
||||
return null;
|
||||
}).when(runtime).execute(any());
|
||||
try (MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
||||
pressure.when(MantleHeapPressure::memoryBudgetBytes).thenReturn(512L << 20);
|
||||
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, false));
|
||||
method.generateChunk(2, 3, listener);
|
||||
method.generateChunk(4, 5, listener);
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
Future<?> closing = executor.submit(() -> {
|
||||
started.countDown();
|
||||
method.close();
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
assertThrows(TimeoutException.class, () -> closing.get(6, TimeUnit.SECONDS));
|
||||
verify(runtime, never()).save();
|
||||
verify(engine, never()).syncGenerationHistory();
|
||||
load.complete(new ChunkLoad(true, null));
|
||||
assertThrows(TimeoutException.class, () -> closing.get(100, TimeUnit.MILLISECONDS));
|
||||
verify(runtime, never()).save();
|
||||
failedLoad.completeExceptionally(new IllegalStateException("chunk failed"));
|
||||
closing.get(5, TimeUnit.SECONDS);
|
||||
verify(listener).onChunkGenerated(2, 3);
|
||||
verify(listener).onChunkFailed(4, 5);
|
||||
verify(engine).syncGenerationHistory();
|
||||
verify(runtime).save();
|
||||
} finally {
|
||||
load.complete(new ChunkLoad(true, null));
|
||||
failedLoad.complete(new ChunkLoad(false, "chunk failed"));
|
||||
executor.shutdownNow();
|
||||
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interruptedCloseDrainsPendingChunksAndWaitsForFinalSave() throws Exception {
|
||||
CompletableFuture<ChunkLoad> load = new CompletableFuture<>();
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
AtomicReference<Thread> closingThread = new AtomicReference<>();
|
||||
AtomicReference<Runnable> queuedSave = new AtomicReference<>();
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CountDownLatch saveQueued = new CountDownLatch(1);
|
||||
when(runtime.isRunning()).thenReturn(true);
|
||||
when(runtime.loadChunk(2, 3)).thenReturn(load);
|
||||
doAnswer(call -> {
|
||||
queuedSave.set(call.getArgument(0, Runnable.class));
|
||||
saveQueued.countDown();
|
||||
return null;
|
||||
}).when(runtime).execute(any());
|
||||
try (MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
||||
pressure.when(MantleHeapPressure::memoryBudgetBytes).thenReturn(256L << 20);
|
||||
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, false));
|
||||
method.generateChunk(2, 3, mock(PregenListener.class));
|
||||
Future<Boolean> closing = executor.submit(() -> {
|
||||
closingThread.set(Thread.currentThread());
|
||||
started.countDown();
|
||||
method.close();
|
||||
return Thread.currentThread().isInterrupted();
|
||||
});
|
||||
assertTrue(started.await(5, TimeUnit.SECONDS));
|
||||
closingThread.get().interrupt();
|
||||
assertThrows(TimeoutException.class, () -> closing.get(100, TimeUnit.MILLISECONDS));
|
||||
verify(runtime, never()).save();
|
||||
verify(engine, never()).syncGenerationHistory();
|
||||
load.complete(new ChunkLoad(true, null));
|
||||
assertTrue(saveQueued.await(5, TimeUnit.SECONDS));
|
||||
assertThrows(TimeoutException.class, () -> closing.get(100, TimeUnit.MILLISECONDS));
|
||||
verify(runtime, never()).save();
|
||||
closingThread.get().interrupt();
|
||||
assertThrows(TimeoutException.class, () -> closing.get(100, TimeUnit.MILLISECONDS));
|
||||
queuedSave.get().run();
|
||||
assertTrue(closing.get(5, TimeUnit.SECONDS));
|
||||
verify(engine).syncGenerationHistory();
|
||||
verify(runtime).save();
|
||||
} finally {
|
||||
load.complete(new ChunkLoad(true, null));
|
||||
saveQueued.await(5, TimeUnit.SECONDS);
|
||||
Runnable pendingSave = queuedSave.get();
|
||||
if (pendingSave != null) {
|
||||
pendingSave.run();
|
||||
}
|
||||
executor.shutdownNow();
|
||||
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serverShutdownClaimsFinalSaveWithoutWaitingForPendingChunks() throws Exception {
|
||||
CompletableFuture<ChunkLoad> load = new CompletableFuture<>();
|
||||
PregenListener listener = mock(PregenListener.class);
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
Thread serverThread = Thread.currentThread();
|
||||
when(runtime.isServerThread()).thenAnswer(call -> Thread.currentThread() == serverThread);
|
||||
when(runtime.isRunning()).thenReturn(true);
|
||||
when(runtime.loadChunk(2, 3)).thenReturn(load);
|
||||
try (MockedStatic<MantleHeapPressure> pressure = mockStatic(MantleHeapPressure.class)) {
|
||||
pressure.when(MantleHeapPressure::memoryBudgetBytes).thenReturn(256L << 20);
|
||||
method = new ModdedPregenMethod(new ModdedPregenMethod.Configuration(runtime, engine, false));
|
||||
method.generateChunk(2, 3, listener);
|
||||
Future<?> closing = executor.submit(method::close);
|
||||
assertThrows(TimeoutException.class, () -> closing.get(100, TimeUnit.MILLISECONDS));
|
||||
assertTrue(method.deferFinalSaveToServerThread());
|
||||
closing.get(5, TimeUnit.SECONDS);
|
||||
verify(runtime, never()).save();
|
||||
assertTrue(method.hasPendingFinalSave());
|
||||
method.completeDeferredFinalSave();
|
||||
verify(runtime).save();
|
||||
verify(runtime, never()).execute(any());
|
||||
} finally {
|
||||
load.complete(new ChunkLoad(true, null));
|
||||
executor.shutdownNow();
|
||||
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedFinalSaveStillRestoresTheEmptyServerPauseSetting() {
|
||||
AtomicInteger pause = new AtomicInteger(60);
|
||||
|
||||
+23
@@ -8,6 +8,7 @@ import art.arcane.volmlib.nativelib.terrain.NativeWorld;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
@@ -66,4 +67,26 @@ public class NativeGeneratorOwnershipTest {
|
||||
assertNull(NativeWorldGenerators.find(world, IrisModdedChunkGenerator.class));
|
||||
assertFalse(nativeGenerator.represents(world));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protocolLookupTracksInstalledGeneratorAndPolicyOwner() {
|
||||
IrisModdedChunkGenerator owner = new IrisModdedChunkGenerator(
|
||||
new NativeGeneratorContext(mock(BiomeSource.class), "example:surface"));
|
||||
NativeModdedChunkGenerator<?, ?, ?> nativeGenerator = (NativeModdedChunkGenerator<?, ?, ?>) owner.nativeGenerator();
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
||||
when(level.dimension()).thenReturn(Level.OVERWORLD);
|
||||
when(level.getChunkSource()).thenReturn(chunks);
|
||||
when(chunks.getGenerator()).thenReturn(nativeGenerator);
|
||||
NativeProtocolWorld world = new NativeProtocolWorld(level);
|
||||
|
||||
assertSame(owner, world.generator(IrisModdedChunkGenerator.class));
|
||||
assertSame(nativeGenerator, world.generator(ChunkGenerator.class));
|
||||
assertNull(world.generator(String.class));
|
||||
|
||||
ChunkGenerator replacement = mock(ChunkGenerator.class);
|
||||
when(chunks.getGenerator()).thenReturn(replacement);
|
||||
assertNull(world.generator(IrisModdedChunkGenerator.class));
|
||||
assertSame(replacement, world.generator(ChunkGenerator.class));
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* 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 art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.modded.NativeMixinFlags;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.terrain.NativeJigsawBounds;
|
||||
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeMixinTarget;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
* Boot audit for the Iris mixin configs. Mixin registration is per-loader (fabric.mod.json entry, NeoForge
|
||||
* mods.toml [[mixins]] block, Forge shadowJar MixinConfigs manifest attribute) and a config that never gets
|
||||
* registered fails silently: no crash, the hooks simply never exist, and entity persistence, custom mob loot
|
||||
* and the Iris world-type labels quietly stop working.
|
||||
*
|
||||
* <p>Application is checked structurally: Mixin transfers an {@code @Inject} handler into the target class,
|
||||
* so the handler's presence on the target proves the mixin applied. Mixin 0.8.7 renames the transferred
|
||||
* method to {@code handler$<ids>$<originalName>}, so the declared name is matched as an exact name or as a
|
||||
* {@code $}-prefixed suffix. Client targets are resolved by name so this class stays free of client
|
||||
* references and is safe on a dedicated server.
|
||||
*/
|
||||
public final class ModdedMixinAudit {
|
||||
private static final String ENTITY_CONFIG = "volmlib.entity.mixins.json";
|
||||
private static final String CLIENT_CONFIG = "volmlib.client.mixins.json";
|
||||
private static final AtomicBoolean AUDITED = new AtomicBoolean(false);
|
||||
|
||||
static final List<ExpectedMixin> EXPECTED = List.of(
|
||||
new ExpectedMixin(ENTITY_CONFIG, "ServerLevelsReadyMixin",
|
||||
NativeMixinTarget.SERVER, "volmlib$levelsReady",
|
||||
false, NativeMixinFlags::levelsReadyRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "JigsawPlacementBoundsMixin",
|
||||
NativeMixinTarget.JIGSAW_PLACEMENT, "volmlib$verticalBounds",
|
||||
false, NativeJigsawBounds::placementBoundsApplied),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "RegistryClientNamesMixin",
|
||||
NativeMixinTarget.REGISTRY_PACKET, "volmlib$clientRegistryNames",
|
||||
false, NativeMixinFlags::registryClientNamesRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "EntityPersistenceMixin",
|
||||
NativeMixinTarget.ENTITY, "iris$applyGeneratedPersistence",
|
||||
false, NativeMixinFlags::entityPersistenceRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "LivingEntityLootMixin",
|
||||
NativeMixinTarget.LIVING_ENTITY, "iris$replaceBaseLoot",
|
||||
false, NativeMixinFlags::livingEntityLootRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "MobAwarenessMixin",
|
||||
NativeMixinTarget.MOB, "iris$tickUnawareMob",
|
||||
false, NativeMixinFlags::mobAwarenessRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "StructureTemplatePaletteConcurrencyMixin",
|
||||
NativeMixinTarget.STRUCTURE_PALETTE,
|
||||
"iris$installConcurrentBlockCache",
|
||||
false, NativeMixinFlags::structureTemplatePaletteRan),
|
||||
new ExpectedMixin(ENTITY_CONFIG, "PackRepositorySelectionMixin",
|
||||
NativeMixinTarget.PACK_REPOSITORY,
|
||||
"volmlib$keepNativeDatapacksLast",
|
||||
false, NativeMixinFlags::packSelectionRan),
|
||||
new ExpectedMixin(CLIENT_CONFIG, "NativeWorldOpenFlowsMixin",
|
||||
NativeMixinTarget.WORLD_OPEN_FLOWS,
|
||||
"volmlib$openWorldCheckWorldStemCompatibility",
|
||||
true, NativeMixinFlags::worldOpenFlowsRan),
|
||||
new ExpectedMixin(CLIENT_CONFIG, "NativeWorldTypeEntryMixin",
|
||||
NativeMixinTarget.WORLD_TYPE_ENTRY,
|
||||
"volmlib$describePreset",
|
||||
true, NativeMixinFlags::worldTypeEntryRan));
|
||||
|
||||
private ModdedMixinAudit() {
|
||||
}
|
||||
|
||||
public static void runOnce() {
|
||||
if (!AUDITED.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
audit(ModdedEngineBootstrap.loader().platformName(),
|
||||
ModdedEngineBootstrap.loader().clientEnvironment());
|
||||
}
|
||||
|
||||
static void reset() {
|
||||
AUDITED.set(false);
|
||||
}
|
||||
|
||||
static void audit(String platform, boolean clientEnvironment) {
|
||||
List<String> missing = new ArrayList<>();
|
||||
List<String> applied = new ArrayList<>();
|
||||
for (ExpectedMixin expected : EXPECTED) {
|
||||
if (expected.clientOnly() && !clientEnvironment) {
|
||||
continue;
|
||||
}
|
||||
if (isApplied(expected)) {
|
||||
applied.add(expected.mixinName() + (expected.ran().getAsBoolean() ? "" : " (not yet exercised)"));
|
||||
} else {
|
||||
missing.add(expected.config() + '/' + expected.mixinName() + " -> " + expected.targetClass());
|
||||
}
|
||||
}
|
||||
if (missing.isEmpty()) {
|
||||
ModdedIrisLog.info("Iris mixin audit ok on {} ({} dist): {}", platform,
|
||||
clientEnvironment ? "client" : "server", String.join(", ", applied));
|
||||
return;
|
||||
}
|
||||
ModdedIrisLog.error("===============================================================");
|
||||
ModdedIrisLog.error("Iris mixin audit FAILED on {} ({} dist): {} of {} expected mixin(s) were not applied.",
|
||||
platform, clientEnvironment ? "client" : "server", missing.size(),
|
||||
missing.size() + applied.size());
|
||||
for (String entry : missing) {
|
||||
ModdedIrisLog.error(" missing: {}", entry);
|
||||
}
|
||||
ModdedIrisLog.error("The mixin config was not registered for this loader (fabric.mod.json mixins, neoforge.mods.toml [[mixins]], forge MixinConfigs manifest attribute).");
|
||||
ModdedIrisLog.error("Entity persistence, custom mob loot, parallel structure safety, Iris datapack priority, readable biome identifiers, or Iris world-type labels are disabled until this is fixed.");
|
||||
ModdedIrisLog.error("===============================================================");
|
||||
}
|
||||
|
||||
private static boolean isApplied(ExpectedMixin expected) {
|
||||
try {
|
||||
return expected.targetClass().hasInjectedHandler(expected.handlerMethod());
|
||||
} catch (LinkageError unavailable) {
|
||||
ModdedIrisLog.warn("Iris mixin audit could not inspect {}", expected.targetClass(), unavailable);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
record ExpectedMixin(String config, String mixinName, NativeMixinTarget targetClass, String handlerMethod,
|
||||
boolean clientOnly, BooleanSupplier ran) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"pack": {
|
||||
"description": "Iris client resources and mod data",
|
||||
"pack_format": 34,
|
||||
"supported_formats": [34, 48]
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedChunkGenerator;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeContainerLoot;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.ProblemReporter;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeGenerationSettings;
|
||||
import net.minecraft.world.level.biome.BiomeSpecialEffects;
|
||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BannerBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerRO;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisModdedBlockEntityParityTest {
|
||||
private static RegistryAccess registries;
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
registries = RegistryAccess.fromRegistryOfRegistries(BuiltInRegistries.REGISTRY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedContainerGetsBlockEntityAndPersistsFilledLoot() {
|
||||
ProtoChunk chunk = newChunk();
|
||||
BlockPos position = new BlockPos(3, 70, 5);
|
||||
chunk.setBlockState(position, Blocks.CHEST.defaultBlockState(), false);
|
||||
|
||||
NativeModdedChunkGenerator.createDefaultBlockEntity(chunk, position, Blocks.CHEST.defaultBlockState());
|
||||
|
||||
BlockEntity blockEntity = chunk.getBlockEntity(position);
|
||||
assertNotNull(blockEntity);
|
||||
assertTrue(blockEntity instanceof Container);
|
||||
Container container = (Container) blockEntity;
|
||||
NativeContainerLoot.fillContainer(container, List.of(new ItemStack(Items.DIAMOND)), new RNG(17L), message -> {});
|
||||
assertEquals(1, countItem(container, Items.DIAMOND));
|
||||
CompoundTag saved = blockEntity.saveWithoutMetadata(registries);
|
||||
ChestBlockEntity restored = new ChestBlockEntity(position, Blocks.CHEST.defaultBlockState());
|
||||
restored.loadWithComponents(saved, registries);
|
||||
assertEquals(1, countItem(restored, Items.DIAMOND));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedSignSpawnerAndBannerGetNativeBlockEntities() {
|
||||
ProtoChunk chunk = newChunk();
|
||||
BlockState signState = Blocks.OAK_SIGN.defaultBlockState();
|
||||
BlockState spawnerState = Blocks.SPAWNER.defaultBlockState();
|
||||
BlockState bannerState = BuiltInRegistries.BLOCK.get(
|
||||
ResourceLocation.parse("minecraft:white_banner")).defaultBlockState();
|
||||
BlockPos signPosition = new BlockPos(1, 70, 1);
|
||||
BlockPos spawnerPosition = new BlockPos(2, 70, 1);
|
||||
BlockPos bannerPosition = new BlockPos(3, 70, 1);
|
||||
|
||||
chunk.setBlockState(signPosition, signState, false);
|
||||
chunk.setBlockState(spawnerPosition, spawnerState, false);
|
||||
chunk.setBlockState(bannerPosition, bannerState, false);
|
||||
NativeModdedChunkGenerator.createDefaultBlockEntity(chunk, signPosition, signState);
|
||||
NativeModdedChunkGenerator.createDefaultBlockEntity(chunk, spawnerPosition, spawnerState);
|
||||
NativeModdedChunkGenerator.createDefaultBlockEntity(chunk, bannerPosition, bannerState);
|
||||
|
||||
assertTrue(chunk.getBlockEntity(signPosition) instanceof SignBlockEntity);
|
||||
assertTrue(chunk.getBlockEntity(spawnerPosition) instanceof SpawnerBlockEntity);
|
||||
assertTrue(chunk.getBlockEntity(bannerPosition) instanceof BannerBlockEntity);
|
||||
}
|
||||
|
||||
private static ProtoChunk newChunk() {
|
||||
return new ProtoChunk(
|
||||
new ChunkPos(0, 0),
|
||||
UpgradeData.EMPTY,
|
||||
LevelHeightAccessor.create(-64, 384),
|
||||
biomeRegistry(),
|
||||
null);
|
||||
}
|
||||
|
||||
private static Registry<Biome> biomeRegistry() {
|
||||
Biome biome = new Biome.BiomeBuilder()
|
||||
.hasPrecipitation(false)
|
||||
.temperature(0.8F)
|
||||
.downfall(0.4F)
|
||||
.specialEffects(new BiomeSpecialEffects.Builder().fogColor(0).waterColor(0x3F76E4)
|
||||
.waterFogColor(0).skyColor(0).build())
|
||||
.mobSpawnSettings(MobSpawnSettings.EMPTY)
|
||||
.generationSettings(BiomeGenerationSettings.EMPTY)
|
||||
.build();
|
||||
MappedRegistry<Biome> registry = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
|
||||
registry.register(ResourceKey.create(Registries.BIOME, ResourceLocation.withDefaultNamespace("plains")),
|
||||
biome, RegistrationInfo.BUILT_IN);
|
||||
return registry.freeze();
|
||||
}
|
||||
|
||||
private static int countItem(Container container, Item item) {
|
||||
int count = 0;
|
||||
for (int slot = 0; slot < container.getContainerSize(); slot++) {
|
||||
ItemStack stack = container.getItem(slot);
|
||||
if (stack.getItem() == item) {
|
||||
count += stack.getCount();
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeSpawnTableMerger;
|
||||
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.util.random.WeightedRandomList;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||
import org.junit.Test;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisModdedChunkGeneratorSpawnTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitSpawnsReplaceMatchingVanillaTypesAndPreserveOthers() {
|
||||
MobSpawnSettings.SpawnerData zombie = new MobSpawnSettings.SpawnerData(EntityType.ZOMBIE, 100, 1, 4);
|
||||
MobSpawnSettings.SpawnerData vanillaSlime = new MobSpawnSettings.SpawnerData(EntityType.SLIME, 1, 1, 1);
|
||||
MobSpawnSettings.SpawnerData explicitSlime = new MobSpawnSettings.SpawnerData(EntityType.SLIME, 7, 2, 5);
|
||||
MobSpawnSettings.SpawnerData explicitCow = new MobSpawnSettings.SpawnerData(EntityType.COW, 3, 2, 4);
|
||||
WeightedRandomList<MobSpawnSettings.SpawnerData> vanilla = WeightedRandomList.create(List.of(
|
||||
zombie,
|
||||
vanillaSlime));
|
||||
WeightedRandomList<MobSpawnSettings.SpawnerData> explicit = WeightedRandomList.create(List.of(
|
||||
explicitSlime,
|
||||
explicitCow));
|
||||
|
||||
WeightedRandomList<MobSpawnSettings.SpawnerData> merged = NativeSpawnTableMerger.merge(vanilla, explicit);
|
||||
List<MobSpawnSettings.SpawnerData> entries = merged.unwrap();
|
||||
|
||||
assertEquals(3, entries.size());
|
||||
assertEquals(EntityType.ZOMBIE, entries.get(0).type);
|
||||
assertEquals(100, entries.get(0).getWeight().asInt());
|
||||
assertEquals(explicitSlime, entries.get(1));
|
||||
assertEquals(7, entries.get(1).getWeight().asInt());
|
||||
assertEquals(explicitCow, entries.get(2));
|
||||
assertEquals(3, entries.get(2).getWeight().asInt());
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntityRuntime;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.ModdedPlatformWorld;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntitySpawns;
|
||||
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.world.entity.IrisEntity;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Difficulty;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.MobSpawnType;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ModdedEntitySpawnDifficultyTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sharedPlatformSpawnReturnsFalseForPeacefulHostiles() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
|
||||
ModdedPlatform platform = new ModdedPlatform(mock(NativeModdedLoader.class));
|
||||
|
||||
assertFalse(platform.spawnEntity(new ModdedPlatformWorld(level), "minecraft:zombie", 0.5, 80, 0.5));
|
||||
|
||||
verify(level, never()).addFreshEntity(any(Entity.class));
|
||||
verify(level, never()).enabledFeatures();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ambientSpawnReturnsNullForPeacefulHostilesBeforeApplyingConfiguration() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
||||
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
|
||||
when(level.getChunkSource()).thenReturn(chunks);
|
||||
when(chunks.getChunkNow(anyInt(), anyInt())).thenReturn(mock(LevelChunk.class));
|
||||
IrisEntity configured = new IrisEntity().setType("zombie").setSpawnEffectRiseOutOfGround(false);
|
||||
|
||||
assertNull(ModdedEntitySpawner.spawn(mock(Engine.class), configured, new NativeEntityRuntime(level), 0, 80, 0, new RNG(1L)));
|
||||
|
||||
verify(level, never()).addFreshEntity(any(Entity.class));
|
||||
verify(level, never()).enabledFeatures();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void peacefulRejectsNativeHostilesBeforeCreation() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
|
||||
|
||||
for (EntityType<?> type : List.of(EntityType.ZOMBIE, EntityType.SKELETON, EntityType.CREEPER,
|
||||
EntityType.SPIDER, EntityType.ENDERMAN, EntityType.BLAZE, EntityType.GHAST,
|
||||
EntityType.PHANTOM, EntityType.SLIME, EntityType.MAGMA_CUBE, EntityType.WITHER,
|
||||
EntityType.PIGLIN_BRUTE, EntityType.ZOGLIN, EntityType.WARDEN)) {
|
||||
assertNull(NativeEntitySpawns.spawn(type, level, BlockPos.ZERO, MobSpawnType.NATURAL));
|
||||
}
|
||||
|
||||
verify(level, never()).enabledFeatures();
|
||||
verify(level, never()).addFreshEntityWithPassengers(any(Entity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void peacefulPreservesPassiveAndNativeMonsterCategoryExceptions() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
|
||||
IllegalStateException reachedNativeFactory = new IllegalStateException("Native entity factory reached");
|
||||
when(level.enabledFeatures()).thenThrow(reachedNativeFactory);
|
||||
|
||||
for (EntityType<?> type : List.of(EntityType.COW, EntityType.VILLAGER, EntityType.WOLF,
|
||||
EntityType.PIGLIN, EntityType.HOGLIN, EntityType.SHULKER, EntityType.ENDER_DRAGON)) {
|
||||
assertSame(reachedNativeFactory, assertThrows(IllegalStateException.class,
|
||||
() -> NativeEntitySpawns.spawn(type, level, BlockPos.ZERO, MobSpawnType.NATURAL)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonPeacefulDifficultiesPreserveHostileSpawns() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
Entity created = mock(Entity.class);
|
||||
|
||||
for (Difficulty difficulty : List.of(Difficulty.EASY, Difficulty.NORMAL, Difficulty.HARD)) {
|
||||
EntityType<?> type = spy(BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.parse("minecraft:zombie")));
|
||||
when(level.getDifficulty()).thenReturn(difficulty);
|
||||
doReturn(created).when(type).spawn(level, BlockPos.ZERO, MobSpawnType.NATURAL);
|
||||
|
||||
assertSame(created, NativeEntitySpawns.spawn(type, level, BlockPos.ZERO, MobSpawnType.NATURAL));
|
||||
verify(type).spawn(level, BlockPos.ZERO, MobSpawnType.NATURAL);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedNativeSpawnFailuresStillPropagate() {
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
when(level.getDifficulty()).thenReturn(Difficulty.PEACEFUL);
|
||||
EntityType<?> type = spy(BuiltInRegistries.ENTITY_TYPE.get(ResourceLocation.parse("minecraft:cow")));
|
||||
IllegalStateException failure = new IllegalStateException("Spawn rejected by platform");
|
||||
doThrow(failure).when(type).spawn(level, BlockPos.ZERO, MobSpawnType.NATURAL);
|
||||
|
||||
assertSame(failure, assertThrows(IllegalStateException.class,
|
||||
() -> NativeEntitySpawns.spawn(type, level, BlockPos.ZERO, MobSpawnType.NATURAL)));
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedStructureStage;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.IdMapper;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiSection;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeGenerationSettings;
|
||||
import net.minecraft.world.level.biome.BiomeSpecialEffects;
|
||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerRO;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ModdedNativeStructurePoiRegistrationTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void visitsOnlyExistingPoiBlocksBeforeNativePlacement() {
|
||||
ProtoChunk chunk = newChunk();
|
||||
BlockPos poiPosition = new BlockPos(3, 70, 5);
|
||||
BlockPos ordinaryPosition = new BlockPos(4, 70, 5);
|
||||
BlockState poiState = Blocks.BARREL.defaultBlockState();
|
||||
chunk.setBlockState(poiPosition, poiState, false);
|
||||
chunk.setBlockState(ordinaryPosition, Blocks.STONE.defaultBlockState(), false);
|
||||
Map<BlockPos, BlockState> visited = new LinkedHashMap<>();
|
||||
|
||||
NativeModdedStructureStage.visitExistingPois(
|
||||
chunk, (position, state) -> visited.put(position.immutable(), state));
|
||||
|
||||
assertEquals(poiState, visited.get(poiPosition));
|
||||
assertFalse(visited.containsKey(ordinaryPosition));
|
||||
assertEquals(1, visited.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicatePrimingIsIdempotentForAnAlreadyRegisteredPoi() {
|
||||
BlockPos position = new BlockPos(3, 70, 5);
|
||||
Holder<PoiType> type = PoiTypes.forState(
|
||||
Blocks.BARREL.defaultBlockState()).orElseThrow();
|
||||
PoiSection section = new PoiSection(() -> { });
|
||||
|
||||
section.add(position, type);
|
||||
section.add(position, type);
|
||||
assertEquals(1L, section.getRecords(candidate -> true, PoiManager.Occupancy.ANY).count());
|
||||
assertEquals(type, section.getType(position).orElseThrow());
|
||||
|
||||
section.remove(position);
|
||||
|
||||
assertTrue(section.getType(position).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void worldgenPrimingQueuesBeforeTheLaterVanillaRemoval() {
|
||||
ProtoChunk chunk = newChunk();
|
||||
BlockPos position = new BlockPos(3, 70, 5);
|
||||
BlockState poiState = Blocks.BARREL.defaultBlockState();
|
||||
chunk.setBlockState(position, poiState, false);
|
||||
PoiSection section = new PoiSection(() -> { });
|
||||
Deque<Runnable> serverQueue = new ArrayDeque<>();
|
||||
AtomicInteger missingRemovals = new AtomicInteger();
|
||||
|
||||
NativeModdedStructureStage.visitExistingPois(chunk,
|
||||
(poiPosition, state) -> queuePoiTransition(
|
||||
serverQueue, section, poiPosition,
|
||||
Blocks.AIR.defaultBlockState(), state, missingRemovals));
|
||||
assertTrue(section.getType(position).isEmpty());
|
||||
queuePoiTransition(serverQueue, section, position,
|
||||
poiState, Blocks.AIR.defaultBlockState(), missingRemovals);
|
||||
|
||||
while (!serverQueue.isEmpty()) {
|
||||
serverQueue.removeFirst().run();
|
||||
}
|
||||
|
||||
assertEquals(0, missingRemovals.get());
|
||||
assertTrue(section.getType(position).isEmpty());
|
||||
}
|
||||
|
||||
private static void queuePoiTransition(Deque<Runnable> serverQueue,
|
||||
PoiSection section,
|
||||
BlockPos position,
|
||||
BlockState oldState,
|
||||
BlockState newState,
|
||||
AtomicInteger missingRemovals) {
|
||||
Optional<Holder<PoiType>> oldType = PoiTypes.forState(oldState);
|
||||
Optional<Holder<PoiType>> newType = PoiTypes.forState(newState);
|
||||
if (Objects.equals(oldType, newType)) {
|
||||
return;
|
||||
}
|
||||
BlockPos immutablePosition = position.immutable();
|
||||
oldType.ifPresent(type -> serverQueue.addLast(() -> {
|
||||
if (section.getType(immutablePosition).isEmpty()) {
|
||||
missingRemovals.incrementAndGet();
|
||||
return;
|
||||
}
|
||||
section.remove(immutablePosition);
|
||||
}));
|
||||
newType.ifPresent(type -> serverQueue.addLast(
|
||||
() -> section.add(immutablePosition, type)));
|
||||
}
|
||||
|
||||
private static ProtoChunk newChunk() {
|
||||
return new ProtoChunk(
|
||||
new ChunkPos(0, 0),
|
||||
UpgradeData.EMPTY,
|
||||
LevelHeightAccessor.create(-64, 384),
|
||||
biomeRegistry(),
|
||||
null);
|
||||
}
|
||||
|
||||
private static Registry<Biome> biomeRegistry() {
|
||||
Biome biome = new Biome.BiomeBuilder()
|
||||
.hasPrecipitation(false)
|
||||
.temperature(0.8F)
|
||||
.downfall(0.4F)
|
||||
.specialEffects(new BiomeSpecialEffects.Builder().fogColor(0).waterColor(0x3F76E4)
|
||||
.waterFogColor(0).skyColor(0).build())
|
||||
.mobSpawnSettings(MobSpawnSettings.EMPTY)
|
||||
.generationSettings(BiomeGenerationSettings.EMPTY)
|
||||
.build();
|
||||
MappedRegistry<Biome> registry = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
|
||||
registry.register(ResourceKey.create(Registries.BIOME, ResourceLocation.withDefaultNamespace("plains")),
|
||||
biome, RegistrationInfo.BUILT_IN);
|
||||
return registry.freeze();
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.server.packs.metadata.pack.PackMetadataSection;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ModdedPackMetadataTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void metadataLoadsClientResourcesAndServerDataForCurrentMinecraft() throws IOException {
|
||||
try (InputStream resource = ModdedPackMetadataTest.class.getResourceAsStream("/pack.mcmeta")) {
|
||||
assertNotNull(resource);
|
||||
try (InputStreamReader reader = new InputStreamReader(resource, StandardCharsets.UTF_8)) {
|
||||
JsonObject metadata = JsonParser.parseReader(reader).getAsJsonObject();
|
||||
for (PackType type : PackType.values()) {
|
||||
PackMetadataSection section = PackMetadataSection.CODEC
|
||||
.parse(JsonOps.INSTANCE, metadata.get("pack")).getOrThrow();
|
||||
assertEquals("Iris client resources and mod data", section.description().getString());
|
||||
assertTrue(type.toString(), section.supportedFormats().orElseThrow().isValueInRange(
|
||||
SharedConstants.getCurrentVersion().getPackVersion(type)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeStructureSetFrequencyOverrides;
|
||||
|
||||
import art.arcane.iris.structure.nativegen.IrisImportedStructureControl;
|
||||
import art.arcane.iris.structure.placement.IrisStructureSetFrequencyOverride;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderOwner;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.biome.Climate;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadStructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
|
||||
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacementType;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class ModdedStructureSetFrequencyOverridesTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapMinecraftRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createStateReplacementScalesOnlyTheExactSetAndRetainsEntries() throws Exception {
|
||||
Holder<StructureSet> complexes = structureSet(
|
||||
"minecraft:nether_complexes", 27, 4, 30084232);
|
||||
Holder<StructureSet> fossils = structureSet(
|
||||
"minecraft:nether_fossils", 2, 1, 14357921);
|
||||
ChunkGeneratorStructureState state = state(List.of(complexes, fossils));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
ChunkGeneratorStructureState returned =
|
||||
NativeStructureSetFrequencyOverrides.apply(state, control);
|
||||
|
||||
assertSame(state, returned);
|
||||
List<Holder<StructureSet>> scaledSets = state.possibleStructureSets();
|
||||
RandomSpreadStructurePlacement scaledComplexes =
|
||||
(RandomSpreadStructurePlacement) scaledSets.get(0).value().placement();
|
||||
assertEquals(26, scaledComplexes.spacing());
|
||||
assertEquals(4, scaledComplexes.separation());
|
||||
assertSame(complexes.value().structures(), scaledSets.get(0).value().structures());
|
||||
assertSame(fossils, scaledSets.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedCustomPlacementTypeRemainsUntouched() {
|
||||
Holder<StructureSet> custom = new BoundStructureSetHolder(
|
||||
"example:custom",
|
||||
new StructureSet(List.of(), new UnsupportedPlacement()));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled =
|
||||
NativeStructureSetFrequencyOverrides.scaleSets(List.of(custom), control);
|
||||
|
||||
assertSame(custom, scaled.getFirst());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unrelatedExclusionCycleRemainsUntouched() {
|
||||
BoundStructureSetHolder first = new BoundStructureSetHolder("example:first");
|
||||
BoundStructureSetHolder second = new BoundStructureSetHolder("example:second");
|
||||
first.bind(new StructureSet(List.of(), placement(32, 8, 1, second)));
|
||||
second.bind(new StructureSet(List.of(), placement(40, 10, 2, first)));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("minecraft:nether_complexes")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled = NativeStructureSetFrequencyOverrides.scaleSets(
|
||||
List.of(first, second), control);
|
||||
|
||||
assertSame(first, scaled.get(0));
|
||||
assertSame(second, scaled.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void affectedExclusionCycleUsesBoundScaledHolders() {
|
||||
BoundStructureSetHolder first = new BoundStructureSetHolder("example:first");
|
||||
BoundStructureSetHolder second = new BoundStructureSetHolder("example:second");
|
||||
first.bind(new StructureSet(List.of(), placement(32, 8, 1, second)));
|
||||
second.bind(new StructureSet(List.of(), placement(40, 10, 2, first)));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("example:first")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
List<Holder<StructureSet>> scaled = NativeStructureSetFrequencyOverrides.scaleSets(
|
||||
List.of(first, second), control);
|
||||
|
||||
assertEquals(31, ((RandomSpreadStructurePlacement)
|
||||
scaled.get(0).value().placement()).spacing());
|
||||
assertEquals(40, ((RandomSpreadStructurePlacement)
|
||||
scaled.get(1).value().placement()).spacing());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void affectedRandomSpreadSubclassFailsInsteadOfLosingSubtypeBehavior() {
|
||||
Holder<StructureSet> custom = new BoundStructureSetHolder(
|
||||
"example:custom",
|
||||
new StructureSet(List.of(), new CustomRandomSpreadPlacement()));
|
||||
KList<IrisStructureSetFrequencyOverride> overrides = new KList<>();
|
||||
overrides.add(new IrisStructureSetFrequencyOverride()
|
||||
.setStructureSet("example:custom")
|
||||
.setMultiplier(1.1D));
|
||||
IrisImportedStructureControl control = new IrisImportedStructureControl()
|
||||
.setFrequencyOverrides(overrides);
|
||||
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
NativeStructureSetFrequencyOverrides.scaleSets(List.of(custom), control));
|
||||
}
|
||||
|
||||
private static Holder<StructureSet> structureSet(
|
||||
String key,
|
||||
int spacing,
|
||||
int separation,
|
||||
int salt
|
||||
) {
|
||||
StructureSet value = new StructureSet(
|
||||
List.of(),
|
||||
new RandomSpreadStructurePlacement(
|
||||
spacing, separation, RandomSpreadType.LINEAR, salt));
|
||||
return new BoundStructureSetHolder(key, value);
|
||||
}
|
||||
|
||||
private static RandomSpreadStructurePlacement placement(
|
||||
int spacing,
|
||||
int separation,
|
||||
int salt,
|
||||
Holder<StructureSet> exclusionTarget
|
||||
) {
|
||||
return new RandomSpreadStructurePlacement(
|
||||
Vec3i.ZERO,
|
||||
StructurePlacement.FrequencyReductionMethod.DEFAULT,
|
||||
1F,
|
||||
salt,
|
||||
Optional.of(new StructurePlacement.ExclusionZone(exclusionTarget, 1)),
|
||||
spacing,
|
||||
separation,
|
||||
RandomSpreadType.LINEAR);
|
||||
}
|
||||
|
||||
private static ChunkGeneratorStructureState state(
|
||||
List<Holder<StructureSet>> sets
|
||||
) throws Exception {
|
||||
Constructor<ChunkGeneratorStructureState> constructor =
|
||||
ChunkGeneratorStructureState.class.getDeclaredConstructor(
|
||||
RandomState.class,
|
||||
BiomeSource.class,
|
||||
long.class,
|
||||
long.class,
|
||||
List.class);
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance(
|
||||
null,
|
||||
new EmptyBiomeSource(),
|
||||
1L,
|
||||
1L,
|
||||
sets);
|
||||
}
|
||||
|
||||
private static final class BoundStructureSetHolder extends Holder.Reference<StructureSet> {
|
||||
private BoundStructureSetHolder(String key) {
|
||||
this(key, null);
|
||||
}
|
||||
|
||||
private BoundStructureSetHolder(String key, StructureSet value) {
|
||||
super(Type.STAND_ALONE, new HolderOwner<>() {
|
||||
}, ResourceKey.create(Registries.STRUCTURE_SET, ResourceLocation.parse(key)), value);
|
||||
}
|
||||
|
||||
private void bind(StructureSet value) {
|
||||
bindValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EmptyBiomeSource extends BiomeSource {
|
||||
@Override
|
||||
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BiomeSource> codec() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Stream<Holder<Biome>> collectPossibleBiomes() {
|
||||
return HolderSet.<Biome>empty().stream();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UnsupportedPlacement extends StructurePlacement {
|
||||
private UnsupportedPlacement() {
|
||||
super(Vec3i.ZERO, FrequencyReductionMethod.DEFAULT, 1F, 0, Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isPlacementChunk(ChunkGeneratorStructureState state, int x, int z) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructurePlacementType<?> type() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CustomRandomSpreadPlacement extends RandomSpreadStructurePlacement {
|
||||
private CustomRandomSpreadPlacement() {
|
||||
super(27, 4, RandomSpreadType.LINEAR, 30084232);
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package art.arcane.volmlib.nativelib.minecraft26_2.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.terrain.NativePregenRuntime;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ChunkResult;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.server.level.TicketType;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NativePregenRuntimeTest {
|
||||
@BeforeClass
|
||||
public static void bootstrapRegistries() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void chunkTicketAdmissionAndReleaseRunThroughTheServerExecutor() {
|
||||
MinecraftServer server = mock(MinecraftServer.class);
|
||||
ServerLevel level = mock(ServerLevel.class);
|
||||
ServerChunkCache chunks = mock(ServerChunkCache.class);
|
||||
when(level.getServer()).thenReturn(server);
|
||||
when(level.getChunkSource()).thenReturn(chunks);
|
||||
CompletableFuture<ChunkResult<ChunkAccess>> loaded = new CompletableFuture<>();
|
||||
doReturn(loaded).when(chunks).getChunkFutureMainThread(-2, 7, ChunkStatus.FULL, true);
|
||||
NativePregenRuntime runtime = NativeModdedPregenRuntime.from(new ModdedPlatformWorld(level));
|
||||
|
||||
CompletableFuture<NativePregenRuntime.ChunkLoad> result = runtime.loadChunk(-2, 7);
|
||||
verify(chunks, never()).addRegionTicket(any(), any(), eq(0), any());
|
||||
verify(chunks, never()).getChunkFutureMainThread(-2, 7, ChunkStatus.FULL, true);
|
||||
ArgumentCaptor<Runnable> requests = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(server).execute(requests.capture());
|
||||
requests.getValue().run();
|
||||
assertFalse(result.isDone());
|
||||
verify(chunks).getChunkFutureMainThread(-2, 7, ChunkStatus.FULL, true);
|
||||
loaded.complete(ChunkResult.of(mock(ChunkAccess.class)));
|
||||
assertTrue(result.join().successful());
|
||||
ArgumentCaptor<TicketType> ticket = ArgumentCaptor.forClass(TicketType.class);
|
||||
verify(chunks).addRegionTicket(ticket.capture(), eq(new ChunkPos(-2, 7)), eq(0), eq(new ChunkPos(-2, 7)));
|
||||
|
||||
runtime.releaseChunk(-2, 7);
|
||||
verify(chunks, never()).removeRegionTicket(any(), any(), eq(0), any());
|
||||
verify(server, times(2)).execute(requests.capture());
|
||||
requests.getAllValues().getLast().run();
|
||||
ArgumentCaptor<TicketType> released = ArgumentCaptor.forClass(TicketType.class);
|
||||
verify(chunks).removeRegionTicket(released.capture(), eq(new ChunkPos(-2, 7)), eq(0), eq(new ChunkPos(-2, 7)));
|
||||
assertSame(ticket.getValue(), released.getValue());
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* 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 art.arcane.volmlib.nativelib.minecraft26_2.modded;
|
||||
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenBiome;
|
||||
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldgenSnapshot.WorldgenPlacedFeature;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.RegistrationInfo;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.resources.RegistryOps;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
|
||||
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
import net.minecraft.world.level.levelgen.structure.Structure;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NativeWorldgenScanTest {
|
||||
private static final String BIOME_JSON = "{\"has_precipitation\":true,\"temperature\":0.8,\"downfall\":0.4,"
|
||||
+ "\"effects\":{\"fog_color\":12638463,\"water_color\":4159204,\"water_fog_color\":329011,\"sky_color\":7907327},"
|
||||
+ "\"carvers\":{},\"features\":[[],[],[],[],[],[],[\"examplemod:ore_glimmer\"]],"
|
||||
+ "\"spawners\":{},\"spawn_costs\":{}}";
|
||||
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanReadsLiveFeatureStepsAndEncodesDefinitions() {
|
||||
NativeWorldgenSnapshot snapshot = NativeWorldgenScan.scan(server(), loader()).encode();
|
||||
|
||||
WorldgenBiome biome = snapshot.biomes().getFirst();
|
||||
assertEquals("examplemod:glade", biome.key());
|
||||
assertEquals(0.8F, biome.temperature(), 0F);
|
||||
assertEquals(0.4F, biome.downfall(), 0.0001F);
|
||||
assertTrue(biome.hasPrecipitation());
|
||||
assertEquals(GenerationStep.Decoration.values().length, biome.featureSteps().size());
|
||||
assertEquals(List.of("examplemod:ore_glimmer"),
|
||||
biome.featureSteps().get(GenerationStep.Decoration.UNDERGROUND_ORES.ordinal()));
|
||||
assertNotNull(biome.definition());
|
||||
assertEquals(0.4F, biome.definition().getAsJsonObject().get("downfall").getAsFloat(), 0.0001F);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unencodableFeaturesStayInTheCatalogWithoutJson() {
|
||||
NativeWorldgenSnapshot snapshot = NativeWorldgenScan.scan(server(), loader()).encode();
|
||||
|
||||
WorldgenPlacedFeature feature = snapshot.placedFeatures().getFirst();
|
||||
assertEquals("examplemod:ore_glimmer", feature.key());
|
||||
assertEquals("", feature.feature());
|
||||
assertNull(feature.config());
|
||||
assertTrue(feature.placement().isJsonArray());
|
||||
assertEquals(1, snapshot.failures().count());
|
||||
assertTrue(snapshot.failures().samples().getFirst().startsWith("feature of examplemod:ore_glimmer"));
|
||||
assertTrue(snapshot.levelStems().isEmpty());
|
||||
}
|
||||
|
||||
private static NativeModdedServer server() {
|
||||
MappedRegistry<PlacedFeature> features = new MappedRegistry<>(Registries.PLACED_FEATURE, Lifecycle.stable());
|
||||
features.register(ResourceKey.create(Registries.PLACED_FEATURE, ResourceLocation.parse("examplemod:ore_glimmer")),
|
||||
new PlacedFeature(Holder.direct(mock(ConfiguredFeature.class)), List.of()), RegistrationInfo.BUILT_IN);
|
||||
features.freeze();
|
||||
MappedRegistry<ConfiguredWorldCarver<?>> carvers = new MappedRegistry<>(Registries.CONFIGURED_CARVER, Lifecycle.stable());
|
||||
carvers.freeze();
|
||||
MappedRegistry<Biome> biomes = new MappedRegistry<>(Registries.BIOME, Lifecycle.stable());
|
||||
MappedRegistry<Structure> structures = new MappedRegistry<>(Registries.STRUCTURE, Lifecycle.stable());
|
||||
structures.freeze();
|
||||
MappedRegistry<StructureSet> structureSets = new MappedRegistry<>(Registries.STRUCTURE_SET, Lifecycle.stable());
|
||||
structureSets.freeze();
|
||||
MappedRegistry<DimensionType> dimensionTypes = new MappedRegistry<>(Registries.DIMENSION_TYPE, Lifecycle.stable());
|
||||
dimensionTypes.freeze();
|
||||
RegistryAccess access = new RegistryAccess.ImmutableRegistryAccess(
|
||||
List.of(biomes, features, carvers, structures, structureSets, dimensionTypes));
|
||||
Biome biome = Biome.DIRECT_CODEC.parse(RegistryOps.create(JsonOps.INSTANCE, access),
|
||||
JsonParser.parseString(BIOME_JSON)).getOrThrow();
|
||||
biomes.register(ResourceKey.create(Registries.BIOME, ResourceLocation.parse("examplemod:glade")), biome, RegistrationInfo.BUILT_IN);
|
||||
biomes.freeze();
|
||||
MinecraftServer server = mock(MinecraftServer.class);
|
||||
when(server.registryAccess()).thenReturn(access.freeze());
|
||||
return NativeModdedServer.fromHandle(server);
|
||||
}
|
||||
|
||||
private static NativeModdedLoader loader() {
|
||||
NativeModdedLoader loader = mock(NativeModdedLoader.class);
|
||||
when(loader.unmodifiedBiome(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||
return loader;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package art.arcane.volmlib.nativelib.minecraft26_2.terrain;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class NativeJigsawBoundsTest {
|
||||
@Test
|
||||
public void verticalDistanceUsesInclusiveBlocksWithinExclusiveHeightLimits() {
|
||||
try (NativeJigsawBounds bounds = NativeJigsawBounds.open(OptionalInt.of(12))) {
|
||||
NativeJigsawBounds.heightLimits(-64, 320);
|
||||
NativeJigsawBounds.center(100);
|
||||
assertEquals(88, NativeJigsawBounds.minimum(40), 0);
|
||||
assertEquals(113, NativeJigsawBounds.maximum(160), 0);
|
||||
|
||||
NativeJigsawBounds.center(-60);
|
||||
assertEquals(-64, NativeJigsawBounds.minimum(-120), 0);
|
||||
assertEquals(-47, NativeJigsawBounds.maximum(0), 0);
|
||||
|
||||
NativeJigsawBounds.center(315);
|
||||
assertEquals(303, NativeJigsawBounds.minimum(255), 0);
|
||||
assertEquals(320, NativeJigsawBounds.maximum(375), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedScopesRestoreTheOuterDistanceAndCenter() {
|
||||
try (NativeJigsawBounds outer = NativeJigsawBounds.open(OptionalInt.of(10))) {
|
||||
NativeJigsawBounds.heightLimits(-64, 320);
|
||||
NativeJigsawBounds.center(80);
|
||||
try (NativeJigsawBounds inner = NativeJigsawBounds.open(OptionalInt.of(2))) {
|
||||
NativeJigsawBounds.heightLimits(0, 256);
|
||||
NativeJigsawBounds.center(200);
|
||||
assertEquals(198, NativeJigsawBounds.minimum(0), 0);
|
||||
assertEquals(203, NativeJigsawBounds.maximum(256), 0);
|
||||
}
|
||||
assertEquals(70, NativeJigsawBounds.minimum(0), 0);
|
||||
assertEquals(91, NativeJigsawBounds.maximum(256), 0);
|
||||
try (NativeJigsawBounds unbounded = NativeJigsawBounds.open(OptionalInt.empty())) {
|
||||
assertEquals(-20, NativeJigsawBounds.minimum(-20), 0);
|
||||
assertEquals(400, NativeJigsawBounds.maximum(400), 0);
|
||||
}
|
||||
assertEquals(70, NativeJigsawBounds.minimum(0), 0);
|
||||
}
|
||||
assertEquals(-20, NativeJigsawBounds.minimum(-20), 0);
|
||||
assertEquals(400, NativeJigsawBounds.maximum(400), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedGenerationClosesTheScopeAndRejectsNegativeDistance() {
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
try (NativeJigsawBounds bounds = NativeJigsawBounds.open(OptionalInt.of(0))) {
|
||||
NativeJigsawBounds.heightLimits(-64, 320);
|
||||
NativeJigsawBounds.center(40);
|
||||
assertEquals(40, NativeJigsawBounds.minimum(-100), 0);
|
||||
assertEquals(41, NativeJigsawBounds.maximum(100), 0);
|
||||
throw new IllegalStateException("generation failed");
|
||||
}
|
||||
});
|
||||
assertEquals(-100, NativeJigsawBounds.minimum(-100), 0);
|
||||
assertEquals(100, NativeJigsawBounds.maximum(100), 0);
|
||||
assertThrows(IllegalArgumentException.class, () -> NativeJigsawBounds.open(OptionalInt.of(-1)));
|
||||
assertEquals(-100, NativeJigsawBounds.minimum(-100), 0);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package art.arcane.volmlib.nativelib.minecraft26_2.terrain;
|
||||
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.server.Bootstrap;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
|
||||
import net.minecraft.world.level.levelgen.structure.pools.StructureTemplatePool;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessorList;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class NativeStructureTemplateAccessTest {
|
||||
@BeforeClass
|
||||
public static void bootstrap() {
|
||||
SharedConstants.tryDetectVersion();
|
||||
Bootstrap.bootStrap();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesRuntimeTemplateThroughWidenedPoolField() {
|
||||
StructureTemplate template = new StructureTemplate();
|
||||
SinglePoolElement element = new RuntimePoolElement(template);
|
||||
assertSame(template, NativeStructureReflection.resolveTemplate(element, () -> {
|
||||
throw new AssertionError("Runtime templates must not require a template manager");
|
||||
}));
|
||||
}
|
||||
|
||||
private static final class RuntimePoolElement extends SinglePoolElement {
|
||||
private RuntimePoolElement(StructureTemplate template) {
|
||||
super(Either.right(template), Holder.direct(new StructureProcessorList(List.of())),
|
||||
StructureTemplatePool.Projection.RIGID, Optional.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
{
|
||||
"global": [
|
||||
{
|
||||
"pattern": "\\bIdentifier\\b",
|
||||
"replacement": "ResourceLocation"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bEntitySpawnReason\\b",
|
||||
"replacement": "MobSpawnType"
|
||||
},
|
||||
{
|
||||
"pattern": "net\\.minecraft\\.world\\.entity\\.animal\\.panda\\.Panda",
|
||||
"replacement": "net.minecraft.world.entity.animal.Panda"
|
||||
},
|
||||
{
|
||||
"pattern": "net\\.minecraft\\.world\\.entity\\.npc\\.villager\\.Villager",
|
||||
"replacement": "net.minecraft.world.entity.npc.Villager"
|
||||
},
|
||||
{
|
||||
"pattern": "net\\.minecraft\\.world\\.level\\.gamerules\\.GameRules",
|
||||
"replacement": "net.minecraft.world.level.GameRules"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bSpeleothemBlock\\b",
|
||||
"replacement": "PointedDripstoneBlock"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bSpeleothemThickness\\b",
|
||||
"replacement": "DripstoneThickness"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bEntityTypes\\b",
|
||||
"replacement": "EntityType"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bAbstractSpreadingStructurePlacement\\b",
|
||||
"replacement": "StructurePlacement"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bDirectPoolAlias\\b",
|
||||
"replacement": "Direct"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bRandomGroupPoolAlias\\b",
|
||||
"replacement": "RandomGroup"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bRandomPoolAlias\\b",
|
||||
"replacement": "Random"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.identifier\\(\\)",
|
||||
"replacement": ".location()"
|
||||
},
|
||||
{
|
||||
"pattern": "ChunkPos\\.pack\\(",
|
||||
"replacement": "ChunkPos.asLong("
|
||||
},
|
||||
{
|
||||
"pattern": "\\.registryAccess\\(\\)\\.lookupOrThrow\\(",
|
||||
"replacement": ".registryAccess().registryOrThrow("
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getPos\\(\\)\\.x\\(\\)",
|
||||
"replacement": ".getPos().x"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getPos\\(\\)\\.z\\(\\)",
|
||||
"replacement": ".getPos().z"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getChunkPos\\(\\)\\.x\\(\\)",
|
||||
"replacement": ".getChunkPos().x"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getChunkPos\\(\\)\\.z\\(\\)",
|
||||
"replacement": ".getChunkPos().z"
|
||||
},
|
||||
{
|
||||
"pattern": "IdentifierArgument",
|
||||
"replacement": "ResourceLocationArgument"
|
||||
},
|
||||
{
|
||||
"pattern": "IdentifierArgument\\.identifier\\(\\)",
|
||||
"replacement": "ResourceLocationArgument.id()"
|
||||
},
|
||||
{
|
||||
"pattern": "BuiltInRegistries\\.([A-Z_]+)\\.getValue\\(",
|
||||
"replacement": "BuiltInRegistries.$1.get("
|
||||
},
|
||||
{
|
||||
"pattern": "\\blevel\\.getMinY\\(",
|
||||
"replacement": "level.getMinBuildHeight("
|
||||
},
|
||||
{
|
||||
"pattern": "(?ms)^ if \\(!Items\\.[A-Z_]+\\.builtInRegistryHolder\\(\\)\\.areComponentsBound\\(\\)\\) \\{\\n.*?^ \\}\\n",
|
||||
"replacement": ""
|
||||
}
|
||||
],
|
||||
"files": {
|
||||
"art/arcane/iris/modded/command/NativeTaggedItemsTest.java": [
|
||||
{
|
||||
"pattern": "\\.getBooleanOr\\(\"selection_tool\", false\\)",
|
||||
"replacement": ".getBoolean(\"selection_tool\")"
|
||||
},
|
||||
{
|
||||
"pattern": "assertTrue\\(item\\.stack\\(\\)\\.get\\(DataComponents\\.TOOLTIP_DISPLAY\\)\\.hiddenComponents\\(\\)\\.contains\\(DataComponents\\.UNBREAKABLE\\)\\);",
|
||||
"replacement": "assertEquals(false, item.stack().get(DataComponents.UNBREAKABLE).showInTooltip());"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/service/ModdedPackUpdateServiceTest.java": [
|
||||
{
|
||||
"pattern": "import net\\.minecraft\\.server\\.permissions\\.LevelBasedPermissionSet;\\n",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "when\\(handle\\.permissions\\(\\)\\)\\.thenReturn\\(LevelBasedPermissionSet\\.ALL\\);",
|
||||
"replacement": "when(handle.hasPermission(2)).thenReturn(false);"
|
||||
},
|
||||
{
|
||||
"pattern": "handle\\.hasPermission\\(",
|
||||
"replacement": "handle.hasPermissions("
|
||||
},
|
||||
{
|
||||
"pattern": "handle\\.nameAndId\\(",
|
||||
"replacement": "handle.getGameProfile("
|
||||
},
|
||||
{
|
||||
"pattern": "handle\\.level\\(\\)",
|
||||
"replacement": "handle.serverLevel()"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/service/ModdedChunkUpdateServiceTest.java": [
|
||||
{
|
||||
"pattern": "when\\(level\\.getMaxY\\(\\)\\)\\.thenReturn\\(319\\);",
|
||||
"replacement": "when(level.getMaxBuildHeight()).thenReturn(320);"
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeSpawnPopulationTest.java": [
|
||||
{
|
||||
"pattern": "when\\(level\\.getMaxY\\(\\)\\)\\.thenReturn\\(320\\);",
|
||||
"replacement": "when(level.getMaxBuildHeight()).thenReturn(321);"
|
||||
},
|
||||
{
|
||||
"pattern": "rules\\.get\\(GameRules\\.SPAWN_MOBS\\)",
|
||||
"replacement": "rules.getBoolean(GameRules.RULE_DOMOBSPAWNING)"
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeTeleportSafetyTest.java": [
|
||||
{
|
||||
"pattern": "when\\(level\\.getMaxY\\(\\)\\)\\.thenReturn\\(16\\);",
|
||||
"replacement": "when(level.getMaxBuildHeight()).thenReturn(17);"
|
||||
},
|
||||
{
|
||||
"pattern": "import net.minecraft.server.level.ServerLevel;",
|
||||
"replacement": "import net.minecraft.server.level.ServerLevel;\nimport net.minecraft.server.level.ChunkResult;\nimport net.minecraft.world.level.chunk.ChunkAccess;"
|
||||
},
|
||||
{
|
||||
"pattern": "CompletableFuture<Void> loaded",
|
||||
"replacement": "CompletableFuture<ChunkResult<ChunkAccess>> loaded"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.addTicketAndLoadWithRadius\\(any\\(\\), any\\(\\), anyInt\\(\\)\\)",
|
||||
"replacement": ".getChunkFutureMainThread(anyInt(), anyInt(), any(), anyBoolean())"
|
||||
},
|
||||
{
|
||||
"pattern": "loaded\\.complete\\(null\\)",
|
||||
"replacement": "loaded.complete(ChunkResult.of(mock(ChunkAccess.class)))"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.removeTicketWithRadius\\(any\\(\\), any\\(\\), anyInt\\(\\)\\)",
|
||||
"replacement": ".removeRegionTicket(any(), any(), anyInt(), any())"
|
||||
},
|
||||
{
|
||||
"pattern": "import static org.mockito.ArgumentMatchers.anyInt;",
|
||||
"replacement": "import static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyBoolean;"
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeStructureOperationsTest.java": [
|
||||
{
|
||||
"pattern": "import net\\.minecraft\\.world\\.level\\.levelgen\\.feature\\.FallenTreeFeature;\\n",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": " assertEquals\\(\"fallen_trees\", NativeStructureOperations\\.classifyFeature\\(mock\\(FallenTreeFeature\\.class\\)\\)\\);\\n",
|
||||
"replacement": ""
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/nativegen/NativeStructureReferenceRepairTest.java": [
|
||||
{
|
||||
"pattern": "\\borigin\\.x\\(\\)",
|
||||
"replacement": "origin.x"
|
||||
},
|
||||
{
|
||||
"pattern": "\\borigin\\.z\\(\\)",
|
||||
"replacement": "origin.z"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bcandidate\\.x\\(\\)",
|
||||
"replacement": "candidate.x"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bcandidate\\.z\\(\\)",
|
||||
"replacement": "candidate.z"
|
||||
},
|
||||
{
|
||||
"pattern": "\\borigin\\.pack\\(",
|
||||
"replacement": "origin.toLong("
|
||||
},
|
||||
{
|
||||
"pattern": "\"lookupOrThrow\"",
|
||||
"replacement": "\"registryOrThrow\""
|
||||
},
|
||||
{
|
||||
"pattern": "\"lookup\"",
|
||||
"replacement": "\"registry\""
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedNativeStructureWorldgenAccessTest.java": [
|
||||
{
|
||||
"pattern": "\\bgenerationCenter\\.x\\(\\)",
|
||||
"replacement": "generationCenter.x"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bgenerationCenter\\.z\\(\\)",
|
||||
"replacement": "generationCenter.z"
|
||||
},
|
||||
{
|
||||
"pattern": "\\borigin\\.pack\\(",
|
||||
"replacement": "origin.toLong("
|
||||
},
|
||||
{
|
||||
"pattern": "\\.updateNeighborsAt\\(",
|
||||
"replacement": ".blockUpdated("
|
||||
},
|
||||
{
|
||||
"pattern": "(Direction\\.(?:NORTH|EAST)), position, position\\.(north|east)\\(\\),\\s*Blocks\\.DIRT\\.defaultBlockState\\(\\),",
|
||||
"replacement": "$1, Blocks.DIRT.defaultBlockState(), position, position.$2(),"
|
||||
},
|
||||
{
|
||||
"pattern": "\"getMinY\"",
|
||||
"replacement": "\"getMinBuildHeight\""
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedTileParityTest.java": [
|
||||
{
|
||||
"pattern": "import net\\.minecraft\\.world\\.level\\.block\\.entity\\.SignTextSlot;\\n",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "import net\\.minecraft\\.world\\.level\\.storage\\.TagValueInput;\\n",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "\\.loadWithComponents\\(TagValueInput\\.create\\(\\s*ProblemReporter\\.DISCARDING, registries, (tile\\.nativeData\\(\\)\\.payload\\(\\)|\\(\\(ModdedTileData\\) tile\\)\\.nativeData\\(\\)\\.payload\\(\\))\\)\\)",
|
||||
"replacement": ".loadWithComponents($1, registries)"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getText\\(SignTextSlot\\.FRONT\\)",
|
||||
"replacement": ".getFrontText()"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getText\\(SignTextSlot\\.BACK\\)",
|
||||
"replacement": ".getBackText()"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getMessages\\(false\\)\\.get\\((\\d+)\\)",
|
||||
"replacement": ".getMessages(false)[$1]"
|
||||
},
|
||||
{
|
||||
"pattern": "out\\.writeShort\\(28\\);",
|
||||
"replacement": "out.writeShort(37);"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedEntitySpawnerParityTest.java": [
|
||||
{
|
||||
"pattern": "MobSpawnType\\.SPAWN_ITEM_USE",
|
||||
"replacement": "MobSpawnType.SPAWN_EGG"
|
||||
},
|
||||
{
|
||||
"pattern": "MobSpawnType\\.LOAD",
|
||||
"replacement": "MobSpawnType.EVENT"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/service/ModdedWorldBoundaryTest.java": [
|
||||
{
|
||||
"pattern": "\\.setSafeZone\\(",
|
||||
"replacement": ".setDamageSafeZone("
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getSafeZone\\(",
|
||||
"replacement": ".getDamageSafeZone("
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeSpawnBiomeTablesTest.java": [
|
||||
{
|
||||
"pattern": "import net.minecraft.resources.ResourceLocation;",
|
||||
"replacement": "import net.minecraft.resources.ResourceLocation;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.core.registries.Registries;"
|
||||
},
|
||||
{
|
||||
"pattern": "registry\\.get\\((ResourceLocation\\.parse\\(\"[^\"]+\"\\))\\)",
|
||||
"replacement": "registry.getHolder(ResourceKey.create(Registries.BIOME, $1))"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/NativeItemTranslatorTest.java": [
|
||||
{
|
||||
"pattern": "assertEquals\\(73F, stack\\.get\\(DataComponents\\.CUSTOM_MODEL_DATA\\)\\.floats\\(\\)\\.getFirst\\(\\), 0F\\);",
|
||||
"replacement": "assertEquals(73, stack.get(DataComponents.CUSTOM_MODEL_DATA).value());"
|
||||
},
|
||||
{
|
||||
"pattern": "\\.getString\\(\"marker\"\\)\\.orElseThrow\\(\\)",
|
||||
"replacement": ".getString(\"marker\")"
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeHarvestSessionTest.java": [
|
||||
{
|
||||
"pattern": "\\.getSelectedItem\\(",
|
||||
"replacement": ".getSelected("
|
||||
},
|
||||
{
|
||||
"pattern": " doAnswer\\(invocation -> \\{ item\\.set\\(invocation\\.getArgument\\(0\\)\\); return null; \\}\\)\\n \\.when\\(inventory\\)\\.setSelectedItem\\(any\\(ItemStack\\.class\\)\\);\\n",
|
||||
"replacement": ""
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/command/IrisModdedCommandsTeleportTest.java": [
|
||||
{
|
||||
"pattern": "\\.addTicketAndLoadWithRadius\\(any\\(\\), any\\(\\), anyInt\\(\\)\\)",
|
||||
"replacement": ".getChunkFutureMainThread(anyInt(), anyInt(), any(), anyBoolean())"
|
||||
},
|
||||
{
|
||||
"pattern": "import static org.mockito.ArgumentMatchers.anyInt;",
|
||||
"replacement": "import static org.mockito.ArgumentMatchers.anyInt;\nimport static org.mockito.ArgumentMatchers.anyBoolean;"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/NativeRegistryAccessTest.java": [
|
||||
{
|
||||
"pattern": "\\.registryAccess\\(\\)\\.registryOrThrow\\(",
|
||||
"replacement": ".registryAccess().lookupOrThrow("
|
||||
},
|
||||
{
|
||||
"pattern": "\\.reloadableRegistries\\(\\)\\.lookup\\(",
|
||||
"replacement": ".reloadableRegistries().get("
|
||||
},
|
||||
{
|
||||
"pattern": "import net.minecraft.core.HolderLookup;",
|
||||
"replacement": "import net.minecraft.core.HolderLookup;\nimport net.minecraft.core.RegistryAccess;"
|
||||
},
|
||||
{
|
||||
"pattern": "HolderLookup\\.Provider reloadedRegistries = mock\\(HolderLookup\\.Provider.class",
|
||||
"replacement": "RegistryAccess.Frozen reloadedRegistries = mock(RegistryAccess.Frozen.class"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedEntityPersistenceTest.java": [
|
||||
{
|
||||
"pattern": "\\.entityTags\\(",
|
||||
"replacement": ".getTags("
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedLootApplierTest.java": [
|
||||
{
|
||||
"pattern": "import net.minecraft.core.BlockPos;",
|
||||
"replacement": "import net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;"
|
||||
},
|
||||
{
|
||||
"pattern": "ChestBlock\\.getConnectedBlockPos\\(leftPos, leftState\\)\\)\\.thenReturn\\(rightPos\\)",
|
||||
"replacement": "ChestBlock.getConnectedDirection(leftState)).thenReturn(Direction.EAST)"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedBlockResolutionContractTest.java": [
|
||||
{
|
||||
"pattern": ", Blocks\\.PALE_MOSS_BLOCK",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "Blocks\\.SULFUR_SPIKE, ",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "\"minecraft:sulfur_spike\", ",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"pattern": "copperChestVariantsReceiveIrisLootAndPostLoadUpdates",
|
||||
"replacement": "chestsReceiveIrisLootAndPostLoadUpdates"
|
||||
},
|
||||
{
|
||||
"pattern": "\"copper_chest\", \"exposed_copper_chest\", \"weathered_copper_chest\",\\s*\"oxidized_copper_chest\", \"waxed_copper_chest\", \"waxed_exposed_copper_chest\",\\s*\"waxed_weathered_copper_chest\", \"waxed_oxidized_copper_chest\"",
|
||||
"replacement": "\"chest\", \"trapped_chest\""
|
||||
},
|
||||
{
|
||||
"pattern": "sulfurAndDripstoneTipsReceivePostLoadUpdates",
|
||||
"replacement": "dripstoneTipsReceivePostLoadUpdates"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedWaterEntitySpawnTest.java": [
|
||||
{
|
||||
"pattern": "(?ms)^ BuiltInRegistries\\.BLOCK\\.prepareTagReload\\(new TagLoader\\.LoadResult<>.*?\\)\\)\\)\\)\\.apply\\(\\);\\n",
|
||||
"replacement": ""
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/command/ModdedLanguageCommandsTest.java": [
|
||||
{
|
||||
"pattern": "new ClickEvent\\.RunCommand\\(",
|
||||
"replacement": "new ClickEvent(ClickEvent.Action.RUN_COMMAND, "
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeCommandTextTest.java": [
|
||||
{
|
||||
"pattern": "\\(\\(ClickEvent\\.RunCommand\\) component\\.getStyle\\(\\)\\.getClickEvent\\(\\)\\)\\.command\\(\\)",
|
||||
"replacement": "component.getStyle().getClickEvent().getValue()"
|
||||
},
|
||||
{
|
||||
"pattern": "\\(\\(HoverEvent\\.ShowText\\) component\\.getStyle\\(\\)\\.getHoverEvent\\(\\)\\)\\.value\\(\\)",
|
||||
"replacement": "component.getStyle().getHoverEvent().getValue(HoverEvent.Action.SHOW_TEXT)"
|
||||
}
|
||||
],
|
||||
"art/arcane/volmlib/nativelib/minecraft26_2/modded/NativeChunkRegenerationTest.java": [
|
||||
{
|
||||
"pattern": "\\.markUnsaved\\(\\)",
|
||||
"replacement": ".setUnsaved(true)"
|
||||
}
|
||||
],
|
||||
"art/arcane/iris/modded/ModdedSavedBiomeConsumersTest.java": [
|
||||
{
|
||||
"pattern": "player\\.level\\(\\)",
|
||||
"replacement": "player.serverLevel()"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user