mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-23 00:28:02 +00:00
dd
This commit is contained in:
1 parent
6111b5670a
commit
402bcb04fe
112 files changed
+5610
-733
No files matched your search
+42
@@ -0,0 +1,42 @@
|
||||
package art.arcane.iris.core.nms.datapack.v1217;
|
||||
|
||||
import art.arcane.iris.core.nms.datapack.IDataFixer.Dimension;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DataFixerV1217ClockTest {
|
||||
@Test
|
||||
public void overworldProvidesTheVanillaDefaultClock() {
|
||||
JSONObject json = fixed(Dimension.OVERWORLD);
|
||||
|
||||
assertEquals("minecraft:overworld", json.getString("default_clock"));
|
||||
assertFalse(json.optBoolean("has_fixed_time", false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void endProvidesItsFixedVanillaClock() {
|
||||
JSONObject json = fixed(Dimension.END);
|
||||
|
||||
assertEquals("minecraft:the_end", json.getString("default_clock"));
|
||||
assertTrue(json.getBoolean("has_fixed_time"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void netherRemainsFixedWithoutADefaultClock() {
|
||||
JSONObject json = fixed(Dimension.NETHER);
|
||||
|
||||
assertFalse(json.has("default_clock"));
|
||||
assertTrue(json.getBoolean("has_fixed_time"));
|
||||
}
|
||||
|
||||
private JSONObject fixed(Dimension dimension) {
|
||||
DataFixerV1217 fixer = new DataFixerV1217();
|
||||
JSONObject json = fixer.resolve(dimension, null);
|
||||
fixer.fixDimension(dimension, json);
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class PackDirectoryResolverTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void resolvesOnlyExistingDirectChildren() throws Exception {
|
||||
File packs = temporaryFolder.newFolder("packs");
|
||||
File overworld = new File(packs, "overworld");
|
||||
Files.createDirectory(overworld.toPath());
|
||||
|
||||
assertEquals(overworld.getAbsoluteFile(), PackDirectoryResolver.resolveExisting(packs, "overworld"));
|
||||
assertEquals(overworld.getAbsoluteFile(), PackDirectoryResolver.resolveExisting(packs, "./overworld"));
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, "missing"));
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsTraversalAbsoluteAndNestedPaths() throws Exception {
|
||||
File packs = temporaryFolder.newFolder("pack-root");
|
||||
File outside = temporaryFolder.newFolder("outside");
|
||||
File nested = new File(packs, "nested/pack");
|
||||
Files.createDirectories(nested.toPath());
|
||||
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, "../outside"));
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, outside.getAbsolutePath()));
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, "nested/pack"));
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, "."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsSymbolicLinkChildren() throws Exception {
|
||||
File packs = temporaryFolder.newFolder("symlink-root");
|
||||
File outside = temporaryFolder.newFolder("symlink-target");
|
||||
Path link = new File(packs, "linked").toPath();
|
||||
try {
|
||||
Files.createSymbolicLink(link, outside.toPath());
|
||||
} catch (IOException | UnsupportedOperationException | SecurityException e) {
|
||||
Assume.assumeNoException(e);
|
||||
}
|
||||
|
||||
assertNull(PackDirectoryResolver.resolveExisting(packs, "linked"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.core.pack;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class PackDownloaderTest {
|
||||
@Test
|
||||
public void resolvesBranchReference() {
|
||||
assertEquals(
|
||||
"https://codeload.github.com/IrisDimensions/overworld/zip/refs/heads/feature/release",
|
||||
PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "feature/release")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesQualifiedHeadReference() {
|
||||
assertEquals(
|
||||
"https://codeload.github.com/IrisDimensions/overworld/zip/refs/heads/master",
|
||||
PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "refs/heads/master")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesTagReference() {
|
||||
assertEquals(
|
||||
"https://codeload.github.com/IrisDimensions/overworld/zip/refs/tags/v4.0.0",
|
||||
PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "refs/tags/v4.0.0")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvesCommitReference() {
|
||||
assertEquals(
|
||||
"https://github.com/IrisDimensions/overworld/archive/8e32852ee6ecd039fae27a36f701f57cdc02e83f.zip",
|
||||
PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "8e32852ee6ecd039fae27a36f701f57cdc02e83f")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsUnsafeRepositoryAndReference() {
|
||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld?raw=1", "master"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("../overworld", "master"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "refs/heads/../master"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", "refs/pull/123/head"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld", ""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.Assume;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackResourceCleanupTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void previewIsReadOnlyAndReturnsSortedCandidates() throws Exception {
|
||||
File pack = createPack("preview");
|
||||
write(pack, "dimensions/main.json", "{\"biome\":\"nested/used\"}");
|
||||
write(pack, "biomes/z-last.json", "{}");
|
||||
write(pack, "biomes/a-first.json", "{}");
|
||||
write(pack, "biomes/nested/used.json", "{}");
|
||||
|
||||
PackResourceCleanup.Preview preview = PackResourceCleanup.preview(pack);
|
||||
|
||||
assertTrue(preview.success());
|
||||
assertTrue(preview.hasCandidates());
|
||||
assertEquals(List.of("biomes/a-first.json", "biomes/z-last.json"), preview.candidatePaths());
|
||||
assertFalse(new File(pack, ".iris-trash").exists());
|
||||
assertTrue(new File(pack, "biomes/a-first.json").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyRunsFreshScanBeforeMutating() throws Exception {
|
||||
File pack = createPack("fresh-scan");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/kept.json", "{}");
|
||||
|
||||
PackResourceCleanup.Preview preview = PackResourceCleanup.preview(pack);
|
||||
assertEquals(List.of("biomes/kept.json"), preview.candidatePaths());
|
||||
|
||||
write(pack, "dimensions/main.json", "{\"biome\":\"kept\"}");
|
||||
PackResourceCleanup.ApplyResult result = PackResourceCleanup.apply(pack);
|
||||
|
||||
assertTrue(result.success());
|
||||
assertFalse(result.changed());
|
||||
assertNull(result.quarantinePath());
|
||||
assertTrue(new File(pack, "biomes/kept.json").isFile());
|
||||
assertFalse(new File(pack, ".iris-trash").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void applyCreatesUniqueDumpsAndPreservesRelativePaths() throws Exception {
|
||||
File pack = createPack("unique-dumps");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/nested/first.json", "{\"value\":1}");
|
||||
|
||||
PackResourceCleanup.ApplyResult first = PackResourceCleanup.apply(pack);
|
||||
assertTrue(first.success());
|
||||
assertEquals(List.of("biomes/nested/first.json"), first.quarantinedPaths());
|
||||
assertTrue(new File(pack, first.quarantinePath() + "/biomes/nested/first.json").isFile());
|
||||
|
||||
write(pack, "biomes/nested/second.json", "{\"value\":2}");
|
||||
PackResourceCleanup.ApplyResult second = PackResourceCleanup.apply(pack);
|
||||
|
||||
assertTrue(second.success());
|
||||
assertNotEquals(first.quarantinePath(), second.quarantinePath());
|
||||
assertTrue(new File(pack, first.quarantinePath() + "/biomes/nested/first.json").isFile());
|
||||
assertTrue(new File(pack, second.quarantinePath() + "/biomes/nested/second.json").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restorePreviewReportsLatestDumpAndAllConflictsWithoutMutation() throws Exception {
|
||||
File pack = createPack("restore-conflict");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/first.json", "{\"value\":1}");
|
||||
PackResourceCleanup.ApplyResult first = PackResourceCleanup.apply(pack);
|
||||
write(pack, "biomes/second.json", "{\"value\":2}");
|
||||
PackResourceCleanup.ApplyResult second = PackResourceCleanup.apply(pack);
|
||||
write(pack, "biomes/second.json", "{\"replacement\":true}");
|
||||
|
||||
PackResourceCleanup.RestorePreview preview = PackResourceCleanup.previewRestore(pack);
|
||||
|
||||
assertTrue(preview.success());
|
||||
assertEquals(second.quarantinePath(), preview.dumpPath());
|
||||
assertEquals(List.of("biomes/second.json"), preview.filePaths());
|
||||
assertEquals(List.of("biomes/second.json"), preview.conflicts());
|
||||
assertTrue(preview.hasConflicts());
|
||||
assertFalse(preview.canRestore());
|
||||
assertTrue(new File(pack, second.quarantinePath() + "/biomes/second.json").isFile());
|
||||
assertTrue(new File(pack, first.quarantinePath() + "/biomes/first.json").isFile());
|
||||
|
||||
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(pack);
|
||||
|
||||
assertFalse(result.success());
|
||||
assertEquals(List.of("biomes/second.json"), result.conflicts());
|
||||
assertTrue(new File(pack, second.quarantinePath() + "/biomes/second.json").isFile());
|
||||
assertEquals("{\"replacement\":true}", read(pack, "biomes/second.json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoreLatestRestoresOnlyNewestDump() throws Exception {
|
||||
File pack = createPack("restore-latest");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/first.json", "{\"value\":1}");
|
||||
PackResourceCleanup.ApplyResult first = PackResourceCleanup.apply(pack);
|
||||
write(pack, "biomes/second.json", "{\"value\":2}");
|
||||
PackResourceCleanup.ApplyResult second = PackResourceCleanup.apply(pack);
|
||||
|
||||
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(pack);
|
||||
|
||||
assertTrue(result.success());
|
||||
assertTrue(result.changed());
|
||||
assertEquals(second.quarantinePath(), result.dumpPath());
|
||||
assertEquals(List.of("biomes/second.json"), result.restoredPaths());
|
||||
assertEquals("{\"value\":2}", read(pack, "biomes/second.json"));
|
||||
assertFalse(new File(pack, "biomes/first.json").exists());
|
||||
assertTrue(new File(pack, first.quarantinePath() + "/biomes/first.json").isFile());
|
||||
assertFalse(new File(pack, second.quarantinePath()).exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoreCopyFailureRollsBackCreatedDestinations() throws Exception {
|
||||
File pack = createPack("restore-rollback");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/a-readable.json", "{\"value\":1}");
|
||||
write(pack, "biomes/z-unreadable.json", "{\"value\":2}");
|
||||
PackResourceCleanup.ApplyResult applied = PackResourceCleanup.apply(pack);
|
||||
Path unreadable = new File(pack, applied.quarantinePath() + "/biomes/z-unreadable.json").toPath();
|
||||
Assume.assumeTrue(Files.getFileStore(unreadable).supportsFileAttributeView("posix"));
|
||||
Set<PosixFilePermission> originalPermissions = Files.getPosixFilePermissions(unreadable);
|
||||
Files.setPosixFilePermissions(unreadable, Set.of(PosixFilePermission.OWNER_WRITE));
|
||||
try {
|
||||
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(pack);
|
||||
|
||||
assertFalse(result.success());
|
||||
assertTrue(result.error().contains("rolled back"));
|
||||
assertFalse(new File(pack, "biomes/a-readable.json").exists());
|
||||
assertFalse(new File(pack, "biomes/z-unreadable.json").exists());
|
||||
assertTrue(new File(pack, applied.quarantinePath() + "/biomes/a-readable.json").isFile());
|
||||
assertTrue(Files.exists(unreadable));
|
||||
} finally {
|
||||
Files.setPosixFilePermissions(unreadable, originalPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoreSourceRemovalFailureRollsBackDestinationsAndPreservesDump() throws Exception {
|
||||
File pack = createPack("restore-delete-rollback");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/a.json", "{\"value\":1}");
|
||||
write(pack, "biomes/b.json", "{\"value\":2}");
|
||||
PackResourceCleanup.ApplyResult applied = PackResourceCleanup.apply(pack);
|
||||
Path quarantineBiomes = new File(pack, applied.quarantinePath() + "/biomes").toPath();
|
||||
Assume.assumeTrue(Files.getFileStore(quarantineBiomes).supportsFileAttributeView("posix"));
|
||||
Set<PosixFilePermission> originalPermissions = Files.getPosixFilePermissions(quarantineBiomes);
|
||||
Files.setPosixFilePermissions(quarantineBiomes, Set.of(
|
||||
PosixFilePermission.OWNER_READ,
|
||||
PosixFilePermission.OWNER_EXECUTE));
|
||||
try {
|
||||
PackResourceCleanup.RestoreResult result = PackResourceCleanup.restoreLatest(pack);
|
||||
|
||||
assertFalse(result.success());
|
||||
assertTrue(result.error().contains("source removal failed"));
|
||||
assertFalse(new File(pack, "biomes/a.json").exists());
|
||||
assertFalse(new File(pack, "biomes/b.json").exists());
|
||||
assertTrue(new File(pack, applied.quarantinePath() + "/biomes/a.json").isFile());
|
||||
assertTrue(new File(pack, applied.quarantinePath() + "/biomes/b.json").isFile());
|
||||
} finally {
|
||||
Files.setPosixFilePermissions(quarantineBiomes, originalPermissions);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cleanupFailsClosedWhenManagedContentContainsSymbolicLink() throws Exception {
|
||||
File pack = createPack("symlink");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
File outside = temporaryFolder.newFile("outside.json");
|
||||
Path link = new File(pack, "biomes/linked.json").toPath();
|
||||
Files.createDirectories(link.getParent());
|
||||
try {
|
||||
Files.createSymbolicLink(link, outside.toPath());
|
||||
} catch (IOException | UnsupportedOperationException | SecurityException e) {
|
||||
Assume.assumeNoException(e);
|
||||
}
|
||||
|
||||
PackResourceCleanup.ApplyResult result = PackResourceCleanup.apply(pack);
|
||||
|
||||
assertFalse(result.success());
|
||||
assertFalse(result.changed());
|
||||
assertTrue(Files.isSymbolicLink(link));
|
||||
assertFalse(new File(pack, ".iris-trash").exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentAppliesSerializePerPack() throws Exception {
|
||||
File pack = createPack("concurrent");
|
||||
write(pack, "dimensions/main.json", "{}");
|
||||
write(pack, "biomes/unused.json", "{}");
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<PackResourceCleanup.ApplyResult> first = executor.submit(() -> {
|
||||
start.await();
|
||||
return PackResourceCleanup.apply(pack);
|
||||
});
|
||||
Future<PackResourceCleanup.ApplyResult> second = executor.submit(() -> {
|
||||
start.await();
|
||||
return PackResourceCleanup.apply(pack);
|
||||
});
|
||||
start.countDown();
|
||||
|
||||
List<PackResourceCleanup.ApplyResult> results = List.of(first.get(), second.get());
|
||||
assertTrue(results.stream().allMatch(PackResourceCleanup.ApplyResult::success));
|
||||
assertEquals(1L, results.stream().filter(PackResourceCleanup.ApplyResult::changed).count());
|
||||
assertFalse(new File(pack, "biomes/unused.json").exists());
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private File createPack(String name) throws Exception {
|
||||
return temporaryFolder.newFolder(name);
|
||||
}
|
||||
|
||||
private void write(File pack, String relativePath, String content) throws Exception {
|
||||
Path path = new File(pack, relativePath).toPath();
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String read(File pack, String relativePath) throws Exception {
|
||||
return Files.readString(new File(pack, relativePath).toPath(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackValidatorCustomBiomeSpawnTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void acceptsExplicitMatchingSpawnGroup() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("monster"));
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsBiomeWithoutCustomSpawns() throws Exception {
|
||||
File biomes = createBiomes("{\"name\":\"Plains\"}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("monster"));
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMissingSpawnGroup() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("monster"));
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("must declare group 'MONSTER'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsImplicitMiscSpawnGroup() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"effects\",\"spawns\":[{\"type\":\"minecraft:armor_stand\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("misc"));
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsSpawnGroupThatDisagreesWithRegistry() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MISC\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("monster"));
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("live entity registry requires 'MONSTER'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsAxolotlSpawnCategory() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"cave\",\"spawns\":[{\"type\":\"minecraft:axolotl\",\"group\":\"AXOLOTLS\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("axolotls"));
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsUnknownSpawnEntity() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"missing:entity\",\"group\":\"MISC\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.unknown());
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("unknown entity type 'missing:entity'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsCaseNormalizedGroupThatRuntimeWouldNotParse() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"monster\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(
|
||||
biomes, key -> PackValidator.SpawnCategoryResolution.known("monster"));
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("unknown group 'monster'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsWrongCustomDerivativeContainerType() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":{}}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("customDerivitives must be an array"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsWrongSpawnContainerType() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":{}}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("spawns must be an array"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsNullCustomDerivativeContainerAsAbsent() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":null}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsNullSpawnContainerAsAbsent() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":null}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void acceptsNamespacedCustomBiomeTags() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:allows_surface_slime_spawns\"]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsUnsafeCustomBiomeTags() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"tags\":[\"minecraft:../outside\"]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, null);
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("invalid tag"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertsSpawnCategoryResolverFailureIntoBlockingError() throws Exception {
|
||||
File biomes = createBiomes("{\"customDerivitives\":[{\"id\":\"swamp\",\"spawns\":[{\"type\":\"minecraft:slime\",\"group\":\"MONSTER\"}]}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateCustomBiomeSpawns(biomes, key -> {
|
||||
throw new IllegalStateException("registry unavailable");
|
||||
});
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("spawn category lookup failed"));
|
||||
assertTrue(errors.get(0).contains("registry unavailable"));
|
||||
}
|
||||
|
||||
private File createBiomes(String json) throws Exception {
|
||||
File biomes = temporaryFolder.newFolder("biomes");
|
||||
File biome = new File(biomes, "test.json");
|
||||
Files.writeString(biome.toPath(), json, StandardCharsets.UTF_8);
|
||||
return biomes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackValidatorReadOnlyTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void validationDoesNotMoveOrRewritePackFiles() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\"}");
|
||||
write(pack, "generators/unused.json", "{\"name\":\"Unused\"}");
|
||||
Map<String, byte[]> before = snapshot(pack.toPath());
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertTrue(result.isLoadable());
|
||||
assertEquals(before.keySet(), snapshot(pack.toPath()).keySet());
|
||||
Map<String, byte[]> after = snapshot(pack.toPath());
|
||||
for (Map.Entry<String, byte[]> entry : before.entrySet()) {
|
||||
assertArrayEquals(entry.getValue(), after.get(entry.getKey()));
|
||||
}
|
||||
assertFalse(new File(pack, ".iris-trash").exists());
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path path = root.toPath().resolve(relative);
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private Map<String, byte[]> snapshot(Path root) throws Exception {
|
||||
Map<String, byte[]> files = new LinkedHashMap<>();
|
||||
try (Stream<Path> stream = Files.walk(root)) {
|
||||
for (Path path : stream.filter(Files::isRegularFile).sorted().toList()) {
|
||||
files.put(root.relativize(path).toString(), Files.readAllBytes(path));
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackValidatorSpawnerEntityTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void acceptsNestedEntitiesReferencedByBothSpawnLists() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/frozen/passive.json", """
|
||||
{
|
||||
"spawns": [{"entity": "standard/passive/cod"}],
|
||||
"initialSpawns": [{"entity": "standard/passive/camel"}]
|
||||
}
|
||||
""");
|
||||
write(pack, "entities/standard/passive/cod.json", "{\"type\":\"COD\",\"surface\":\"WATER\"}");
|
||||
write(pack, "entities/standard/passive/camel.json", "{\"type\":\"CAMEL\"}");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMalformedContainersAndEntries() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/broken.json", """
|
||||
{
|
||||
"spawns": null,
|
||||
"initialSpawns": [7, {}, {"entity": 9}, {"entity": " "}]
|
||||
}
|
||||
""");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertEquals(5, errors.size());
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("spawns must be an array")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("non-object entry")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("without an entity reference")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("must be a string")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("blank entity reference")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMissingAndMalformedReferencedEntities() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/broken.json", """
|
||||
{
|
||||
"spawns": [
|
||||
{"entity": "standard/hostile/missing"},
|
||||
{"entity": "standard/hostile/broken"}
|
||||
]
|
||||
}
|
||||
""");
|
||||
write(pack, "entities/standard/hostile/broken.json", "not-json");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertEquals(2, errors.size());
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("references missing entity 'standard/hostile/missing'")));
|
||||
assertTrue(errors.stream().anyMatch(error -> error.contains("references malformed entity 'standard/hostile/broken'")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMalformedSpawnerJson() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/broken.json", "not-json");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("Spawner 'broken' has invalid JSON"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsEntityReferenceThatEscapesPack() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/broken.json", "{\"spawns\":[{\"entity\":\"../outside\"}]}");
|
||||
write(pack, "outside.json", "{\"type\":\"COW\"}");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertEquals(1, errors.size());
|
||||
assertTrue(errors.get(0).contains("unsafe entity reference '../outside'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresMalformedEntityThatNoSpawnerReferences() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "spawners/passive.json", "{\"spawns\":[{\"entity\":\"standard/passive/cow\"}]}");
|
||||
write(pack, "entities/standard/passive/cow.json", "{\"type\":\"COW\"}");
|
||||
write(pack, "entities/unique/unused.json", "not-json");
|
||||
|
||||
List<String> errors = validate(pack);
|
||||
|
||||
assertTrue(errors.isEmpty());
|
||||
}
|
||||
|
||||
private List<String> validate(File pack) {
|
||||
return PackValidator.validateSpawnerEntityReferences(
|
||||
new File(pack, "spawners"), new File(pack, "entities"));
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path path = root.toPath().resolve(relative);
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package art.arcane.iris.core.pack;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PackValidatorStructureTransformTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void reportsUnsupportedTransformsAcrossEveryStructureHost() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"structures\":[{\"structures\":[\"test\"],\"rotation\":{}}]}");
|
||||
write(pack, "regions/nested/region.json", "{\"structures\":[{\"structures\":[\"test\"],\"translate\":{}}]}");
|
||||
write(pack, "biomes/nested/biome.json", "{\"structures\":[{\"structures\":[\"test\"],\"scale\":{}}]}");
|
||||
|
||||
List<String> errors = PackValidator.validateUnsupportedStructureTransforms(pack);
|
||||
|
||||
assertEquals(List.of(
|
||||
"Dimension 'main' structures[0] declares unsupported field 'rotation'. Structure placement transforms are not supported; remove the field.",
|
||||
"Region 'nested/region' structures[0] declares unsupported field 'translate'. Structure placement transforms are not supported; remove the field.",
|
||||
"Biome 'nested/biome' structures[0] declares unsupported field 'scale'. Structure placement transforms are not supported; remove the field."
|
||||
), errors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoresTransformsOutsideStructurePlacementArrays() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "biomes/biome.json", "{\"objects\":[{\"rotation\":{},\"translate\":{},\"scale\":{}}],\"structures\":[{\"structures\":[\"test\"]}]}");
|
||||
|
||||
assertTrue(PackValidator.validateUnsupportedStructureTransforms(pack).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unsupportedTransformBlocksFullPackValidation() throws Exception {
|
||||
File pack = temporaryFolder.newFolder("pack");
|
||||
write(pack, "dimensions/main.json", "{\"regions\":[\"region\"],\"structures\":[{\"structures\":[\"test\"],\"rotation\":null}]}");
|
||||
write(pack, "regions/region.json", "{\"landBiomes\":[\"biome\"]}");
|
||||
write(pack, "biomes/biome.json", "{\"name\":\"Biome\"}");
|
||||
|
||||
PackValidationResult result = PackValidator.validate(pack);
|
||||
|
||||
assertFalse(result.isLoadable());
|
||||
assertTrue(result.getBlockingErrors().contains(
|
||||
"Dimension 'main' structures[0] declares unsupported field 'rotation'. Structure placement transforms are not supported; remove the field."));
|
||||
}
|
||||
|
||||
private void write(File root, String relative, String content) throws Exception {
|
||||
Path path = root.toPath().resolve(relative);
|
||||
Files.createDirectories(path.getParent());
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,10 @@ import java.io.PrintStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisPregeneratorInitTest {
|
||||
@@ -53,6 +55,35 @@ public class IrisPregeneratorInitTest {
|
||||
assertTrue(output.contains("Pregen finished: generated=8 total=9 failed=1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cancellationSummaryDoesNotReportPartialRunAsFinished() {
|
||||
IrisSettings previousSettings = IrisSettings.settings;
|
||||
PrintStream previousOut = System.out;
|
||||
IrisSettings.settings = new IrisSettings();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8));
|
||||
AtomicReference<IrisPregenerator> pregeneratorReference = new AtomicReference<>();
|
||||
CancelAfterFirstChunkMethod method = new CancelAfterFirstChunkMethod(pregeneratorReference);
|
||||
PregenTask task = PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(1)
|
||||
.radiusZ(1)
|
||||
.build();
|
||||
try {
|
||||
IrisPregenerator pregenerator = new IrisPregenerator(task, method, new NoOpPregenListener());
|
||||
pregeneratorReference.set(pregenerator);
|
||||
|
||||
pregenerator.start();
|
||||
|
||||
String summary = output.toString(StandardCharsets.UTF_8);
|
||||
assertTrue(summary.contains("Pregen cancelled: generated=1 total=9 failed=0 remaining=8"));
|
||||
assertFalse(summary.contains("Pregen finished:"));
|
||||
} finally {
|
||||
System.setOut(previousOut);
|
||||
IrisSettings.settings = previousSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private String runCompletionOnClose(boolean failLast) {
|
||||
IrisSettings previousSettings = IrisSettings.settings;
|
||||
PrintStream previousOut = System.out;
|
||||
@@ -173,6 +204,55 @@ public class IrisPregeneratorInitTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CancelAfterFirstChunkMethod implements PregeneratorMethod {
|
||||
private final AtomicReference<IrisPregenerator> pregeneratorReference;
|
||||
private boolean cancelled;
|
||||
|
||||
private CancelAfterFirstChunkMethod(AtomicReference<IrisPregenerator> pregeneratorReference) {
|
||||
this.pregeneratorReference = pregeneratorReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
listener.onChunkGenerated(x, z);
|
||||
if (!cancelled) {
|
||||
cancelled = true;
|
||||
pregeneratorReference.get().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NoOpPregenListener implements PregenListener {
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package art.arcane.iris.core.project;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisEntitySpawn;
|
||||
import art.arcane.iris.engine.object.IrisSpawner;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisProjectEntityDependencyTest {
|
||||
@Test
|
||||
public void collectsInitialOnlyEntityAlongsideNormalSpawnEntityForExport() {
|
||||
IrisSpawner spawner = new IrisSpawner();
|
||||
spawner.getSpawns().add(new IrisEntitySpawn().setEntity("standard/passive/cow"));
|
||||
spawner.getInitialSpawns().add(new IrisEntitySpawn().setEntity("beta/sentinel"));
|
||||
KSet<IrisSpawner> spawners = new KSet<>();
|
||||
spawners.add(spawner);
|
||||
|
||||
KSet<String> entityKeys = IrisProject.collectSpawnerEntityKeys(spawners);
|
||||
|
||||
assertEquals(2, entityKeys.size());
|
||||
assertTrue(entityKeys.contains("standard/passive/cow"));
|
||||
assertTrue(entityKeys.contains("beta/sentinel"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNoEntityDependenciesForEmptySpawnerSet() {
|
||||
KSet<String> entityKeys = IrisProject.collectSpawnerEntityKeys(new KSet<>());
|
||||
|
||||
assertTrue(entityKeys.isEmpty());
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
package art.arcane.iris.core.project;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.object.IrisJigsawPiece;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
@@ -50,6 +54,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SchemaBuilderParityTest {
|
||||
private static final List<String> POTION_KEYS = List.of("minecraft:speed", "minecraft:slow_falling", "sniffer_mod:mega_boost");
|
||||
@@ -108,6 +116,26 @@ public class SchemaBuilderParityTest {
|
||||
assertEquals(List.of("ALPHA", "BETA"), enumValues(schema.getJSONObject("definitions"), flavorDefinitionKey()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void structurePlacementSchemaOmitsUnsupportedTransforms() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisStructure> structureLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisJigsawPiece> pieceLoader = mock(ResourceLoader.class);
|
||||
when(data.getStructureLoader()).thenReturn(structureLoader);
|
||||
when(data.getJigsawPieceLoader()).thenReturn(pieceLoader);
|
||||
when(structureLoader.getPossibleKeys()).thenReturn(new String[0]);
|
||||
when(pieceLoader.getPossibleKeys()).thenReturn(new String[0]);
|
||||
|
||||
JSONObject properties = new SchemaBuilder(IrisStructurePlacement.class, data)
|
||||
.construct().getJSONObject("properties");
|
||||
|
||||
assertTrue(properties.has("structures"));
|
||||
assertTrue(properties.has("distribution"));
|
||||
assertFalse(properties.has("rotation"));
|
||||
assertFalse(properties.has("translate"));
|
||||
assertFalse(properties.has("scale"));
|
||||
}
|
||||
|
||||
private static String flavorDefinitionKey() {
|
||||
return "enum-" + Flavor.class.getCanonicalName().replaceAll("\\Q.\\E", "-").toLowerCase();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package art.arcane.iris.core.tools;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisToolbeltPackReferenceTest {
|
||||
@Test
|
||||
public void plainPackUsesMatchingDimensionKey() {
|
||||
IrisToolbelt.PackReference reference = IrisToolbelt.parsePackReference("overworld");
|
||||
|
||||
assertEquals("overworld", reference.pack());
|
||||
assertEquals("overworld", reference.dimension());
|
||||
assertFalse(reference.explicitDimension());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitDimensionKeepsPackAndDimensionSeparate() {
|
||||
IrisToolbelt.PackReference reference = IrisToolbelt.parsePackReference(" custom_pack : dimensions/sky ");
|
||||
|
||||
assertEquals("custom_pack", reference.pack());
|
||||
assertEquals("dimensions/sky", reference.dimension());
|
||||
assertTrue(reference.explicitDimension());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedReferencesAreRejected() {
|
||||
assertNull(IrisToolbelt.parsePackReference(null));
|
||||
assertNull(IrisToolbelt.parsePackReference(""));
|
||||
assertNull(IrisToolbelt.parsePackReference(":"));
|
||||
assertNull(IrisToolbelt.parsePackReference("pack:"));
|
||||
assertNull(IrisToolbelt.parsePackReference(":dimension"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package art.arcane.iris.engine;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisGenerator;
|
||||
import art.arcane.iris.engine.object.IrisInterpolator;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation.NoiseBounds;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation.NoiseBoundsProvider;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.mockito.Answers.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisComplexGridBoundsCacheTest {
|
||||
@Test
|
||||
public void gridBoundsCacheIsIsolatedPerComplex() throws Exception {
|
||||
IrisComplex first = createComplex();
|
||||
IrisComplex second = createComplex();
|
||||
Method cornerBounds = cornerBoundsMethod();
|
||||
|
||||
long firstPacked = invokeCornerBounds(first, cornerBounds, new CountingInterpolator(1.25D, 2.5D), 64, -32);
|
||||
long secondPacked = invokeCornerBounds(second, cornerBounds, new CountingInterpolator(10.25D, 20.5D), 64, -32);
|
||||
|
||||
assertNotEquals(firstPacked, secondPacked);
|
||||
assertEquals(1.25F, unpackLow(firstPacked), 0D);
|
||||
assertEquals(2.5F, unpackHigh(firstPacked), 0D);
|
||||
assertEquals(10.25F, unpackLow(secondPacked), 0D);
|
||||
assertEquals(20.5F, unpackHigh(secondPacked), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gridBoundsCacheReusesCornersWithinSameComplex() throws Exception {
|
||||
IrisComplex complex = createComplex();
|
||||
Method cornerBounds = cornerBoundsMethod();
|
||||
CountingInterpolator interpolator = new CountingInterpolator(3.5D, 7.25D);
|
||||
|
||||
long firstPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
|
||||
long secondPacked = invokeCornerBounds(complex, cornerBounds, interpolator, 128, 96);
|
||||
|
||||
assertEquals(firstPacked, secondPacked);
|
||||
assertEquals(1, interpolator.getInvocations());
|
||||
}
|
||||
|
||||
private IrisComplex createComplex() throws Exception {
|
||||
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
|
||||
|
||||
Field generatorBounds = IrisComplex.class.getDeclaredField("generatorBounds");
|
||||
generatorBounds.setAccessible(true);
|
||||
generatorBounds.set(complex, new HashMap<>());
|
||||
|
||||
Class<?> cacheClass = Class.forName("art.arcane.iris.engine.IrisComplex$GridBoundsCache");
|
||||
Constructor<?> cacheConstructor = cacheClass.getDeclaredConstructor();
|
||||
cacheConstructor.setAccessible(true);
|
||||
ThreadLocal<Object> cache = ThreadLocal.withInitial(() -> newCache(cacheConstructor));
|
||||
Field gridBoundsCache = IrisComplex.class.getDeclaredField("gridBoundsCache");
|
||||
gridBoundsCache.setAccessible(true);
|
||||
gridBoundsCache.set(complex, cache);
|
||||
return complex;
|
||||
}
|
||||
|
||||
private Object newCache(Constructor<?> constructor) {
|
||||
try {
|
||||
return constructor.newInstance();
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Method cornerBoundsMethod() throws Exception {
|
||||
Class<?> cacheClass = Class.forName("art.arcane.iris.engine.IrisComplex$GridBoundsCache");
|
||||
Method method = IrisComplex.class.getDeclaredMethod(
|
||||
"cornerBounds",
|
||||
cacheClass,
|
||||
Engine.class,
|
||||
IrisInterpolator.class,
|
||||
int.class,
|
||||
Set.class,
|
||||
int.class,
|
||||
int.class
|
||||
);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}
|
||||
|
||||
private long invokeCornerBounds(IrisComplex complex, Method method, IrisInterpolator interpolator, int x, int z) throws Exception {
|
||||
Field gridBoundsCache = IrisComplex.class.getDeclaredField("gridBoundsCache");
|
||||
gridBoundsCache.setAccessible(true);
|
||||
ThreadLocal<?> cache = (ThreadLocal<?>) gridBoundsCache.get(complex);
|
||||
return (long) method.invoke(complex, cache.get(), null, interpolator, 0, Set.<IrisGenerator>of(), x, z);
|
||||
}
|
||||
|
||||
private float unpackLow(long packed) {
|
||||
return Float.intBitsToFloat((int) (packed >>> 32));
|
||||
}
|
||||
|
||||
private float unpackHigh(long packed) {
|
||||
return Float.intBitsToFloat((int) packed);
|
||||
}
|
||||
|
||||
private static final class CountingInterpolator extends IrisInterpolator {
|
||||
private final NoiseBounds bounds;
|
||||
private final AtomicInteger invocations = new AtomicInteger();
|
||||
|
||||
private CountingInterpolator(double low, double high) {
|
||||
bounds = new NoiseBounds(low, high);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||
invocations.incrementAndGet();
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private int getInvocations() {
|
||||
return invocations.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package art.arcane.iris.engine.actuator;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineMetrics;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
import art.arcane.iris.spi.PlatformBiomeWriter;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.project.context.ChunkedDataCache;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisBiomeActuatorCoordinateTest {
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void derivativeScatterUsesEachWorldColumnCoordinate() {
|
||||
bindPlatform();
|
||||
Engine engine = engine();
|
||||
IrisBiomeActuator actuator = new IrisBiomeActuator(engine);
|
||||
Hunk<PlatformBiome> output = mock(Hunk.class);
|
||||
when(output.getWidth()).thenReturn(2);
|
||||
when(output.getDepth()).thenReturn(2);
|
||||
when(output.getHeight()).thenReturn(1);
|
||||
|
||||
List<String> samples = new ArrayList<>();
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
when(biome.getSkyBiomeKey(any(RNG.class), same(engine), anyDouble(), anyDouble(), anyDouble()))
|
||||
.thenAnswer(invocation -> {
|
||||
double worldX = invocation.getArgument(2);
|
||||
double worldZ = invocation.getArgument(4);
|
||||
samples.add((int) worldX + "," + (int) worldZ);
|
||||
return "minecraft:plains";
|
||||
});
|
||||
|
||||
ChunkedDataCache<IrisBiome> biomeCache = mock(ChunkedDataCache.class);
|
||||
when(biomeCache.get(anyInt(), anyInt())).thenReturn(biome);
|
||||
ChunkContext context = mock(ChunkContext.class);
|
||||
when(context.getBiome()).thenReturn(biomeCache);
|
||||
|
||||
actuator.onActuate(100, -200, output, false, context);
|
||||
|
||||
assertEquals(List.of("100,-200", "100,-199", "101,-200", "101,-199"), samples);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Engine engine() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
when(engineMantle.getMantle()).thenReturn(mantle);
|
||||
SeedManager seedManager = mock(SeedManager.class);
|
||||
when(seedManager.getBiome()).thenReturn(1337L);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getCacheID()).thenReturn(1);
|
||||
when(engine.getSeedManager()).thenReturn(seedManager);
|
||||
when(engine.getMantle()).thenReturn(engineMantle);
|
||||
when(engine.getMetrics()).thenReturn(new EngineMetrics(16));
|
||||
return engine;
|
||||
}
|
||||
|
||||
private void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBiome biome = mock(PlatformBiome.class);
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.biome(anyString())).thenReturn(biome);
|
||||
when(registries.block(anyString())).thenReturn(block);
|
||||
PlatformBiomeWriter biomeWriter = mock(PlatformBiomeWriter.class);
|
||||
when(biomeWriter.biomeIdFor(anyString())).thenReturn(1);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
when(platform.biomeWriter()).thenReturn(biomeWriter);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package art.arcane.iris.engine.decorator;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||
import art.arcane.iris.engine.object.IrisDecorator;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisDecoratorCaveContextTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void explicitCaveContextSkipsFluidWithoutMutatingBiome() {
|
||||
Engine engine = mock(Engine.class);
|
||||
SeedManager seedManager = mock(SeedManager.class);
|
||||
doReturn(seedManager).when(engine).getSeedManager();
|
||||
doReturn(mock(IrisData.class)).when(engine).getData();
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
doReturn(63).when(dimension).getFluidHeight();
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
|
||||
IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
doReturn(true).when(decorator).passesChanceGate(any(), anyDouble(), anyDouble(), any());
|
||||
doReturn(true).when(decorator).isStacking();
|
||||
doReturn(true).when(decorator).isForcePlace();
|
||||
doReturn(1).when(decorator).getHeight(any(), anyDouble(), anyDouble(), any());
|
||||
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
doReturn(InferredType.LAND).when(biome).getInferredType();
|
||||
doReturn(new IrisDecorator[]{decorator}).when(biome).getDecoratorBucket(IrisDecorationPart.NONE);
|
||||
doReturn(new IrisDecorator[]{decorator}).when(biome).getDecoratorBucket(IrisDecorationPart.CEILING);
|
||||
|
||||
PlatformBlockState fluid = mock(PlatformBlockState.class);
|
||||
doReturn(true).when(fluid).isFluid();
|
||||
Hunk<PlatformBlockState> output = mock(Hunk.class);
|
||||
doReturn(128).when(output).getHeight();
|
||||
doReturn(fluid).when(output).get(0, 10, 0);
|
||||
|
||||
IrisSurfaceDecorator surfaceDecorator = new IrisSurfaceDecorator(engine);
|
||||
surfaceDecorator.decorate(0, 0, 0, 0, 0, 0, 0, 0, output, biome, InferredType.CAVE, 10, 10);
|
||||
DecoratorCore.PlaceOpts surfaceOptions = DecoratorCore.SCRATCH_OPTS.get();
|
||||
assertTrue(surfaceOptions.caveSkipFluid);
|
||||
assertFalse(surfaceOptions.underwater);
|
||||
|
||||
IrisCeilingDecorator ceilingDecorator = new IrisCeilingDecorator(engine);
|
||||
ceilingDecorator.decorate(0, 0, 0, 0, 0, 0, 0, 0, output, biome, InferredType.CAVE, 10, 10);
|
||||
DecoratorCore.PlaceOpts ceilingOptions = DecoratorCore.SCRATCH_OPTS.get();
|
||||
assertTrue(ceilingOptions.caveSkipFluid);
|
||||
assertEquals(InferredType.LAND, biome.getInferredType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullInferenceRemainsSafeForNormalSurfaceContext() {
|
||||
assertTrue(IrisSurfaceDecorator.isUnderwater(null, 10, 63));
|
||||
assertFalse(IrisSurfaceDecorator.skipsFluid(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package art.arcane.iris.engine.framework;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class StructurePlacementMarkerTest {
|
||||
@Test
|
||||
public void structureMarkerRoundTripsDelimiterSafeKeys() {
|
||||
String objectKey = "pieces/room@east:alpha/樹";
|
||||
String structureKey = "structures/village@night:beta/城";
|
||||
|
||||
String encoded = StructurePlacementMarker.encodeStructure(objectKey, 918273, structureKey);
|
||||
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode(encoded);
|
||||
|
||||
assertTrue(encoded.startsWith("@iris-structure:v1:"));
|
||||
assertEquals(objectKey, decoded.objectKey());
|
||||
assertEquals(918273, decoded.placementId());
|
||||
assertEquals(structureKey, decoded.structureKey());
|
||||
assertTrue(decoded.structureAware());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyMarkerDecodesWithoutChangingFields() {
|
||||
StructurePlacementMarker.Decoded decoded = StructurePlacementMarker.decode("objects/oak_tree@42");
|
||||
|
||||
assertEquals("objects/oak_tree", decoded.objectKey());
|
||||
assertEquals(42, decoded.placementId());
|
||||
assertNull(decoded.structureKey());
|
||||
assertFalse(decoded.structureAware());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void malformedAndUnknownMarkersFailSafely() {
|
||||
assertNull(StructurePlacementMarker.decode(null));
|
||||
assertNull(StructurePlacementMarker.decode(""));
|
||||
assertNull(StructurePlacementMarker.decode("objects/oak_tree"));
|
||||
assertNull(StructurePlacementMarker.decode("objects/oak_tree@not-an-id"));
|
||||
assertNull(StructurePlacementMarker.decode("objects/oak_tree@42@extra"));
|
||||
assertNull(StructurePlacementMarker.decode("@iris-structure:v2:b2JqZWN0:42:c3RydWN0dXJl"));
|
||||
assertNull(StructurePlacementMarker.decode("@iris-structure:v1:not%base64:42:c3RydWN0dXJl"));
|
||||
assertNull(StructurePlacementMarker.decode("@iris-structure:v1:b2JqZWN0:42:"));
|
||||
}
|
||||
}
|
||||
+43
@@ -37,9 +37,11 @@ import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
@@ -117,6 +119,43 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
assertEquals(firstCapture.carvedLiquids, secondCapture.carvedLiquids);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void warpCacheDoesNotCrossContaminateCarverInstancesOnSameThread() throws Exception {
|
||||
Engine engine = createEngine(128, 92);
|
||||
IrisCaveProfile firstProfile = createProfile(true, false);
|
||||
firstProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D));
|
||||
IrisCaveProfile secondProfile = createProfile(true, false);
|
||||
secondProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D));
|
||||
IrisCaveProfile controlProfile = createProfile(true, false);
|
||||
controlProfile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.CELLULAR).zoomed(0.12D));
|
||||
IrisCaveCarver3D firstCarver = new IrisCaveCarver3D(engine, firstProfile);
|
||||
IrisCaveCarver3D secondCarver = new IrisCaveCarver3D(engine, secondProfile);
|
||||
IrisCaveCarver3D controlCarver = new IrisCaveCarver3D(engine, controlProfile);
|
||||
int x = 48;
|
||||
int y = 64;
|
||||
int z = -352;
|
||||
|
||||
double firstDensity = sampleDensity(firstCarver, x, y, z);
|
||||
double secondDensity = sampleDensity(secondCarver, x, y, z);
|
||||
AtomicReference<Double> controlDensity = new AtomicReference<>();
|
||||
AtomicReference<Throwable> controlFailure = new AtomicReference<>();
|
||||
Thread controlThread = new Thread(() -> {
|
||||
try {
|
||||
controlDensity.set(sampleDensity(controlCarver, x, y, z));
|
||||
} catch (Throwable throwable) {
|
||||
controlFailure.set(throwable);
|
||||
}
|
||||
}, "Iris cave warp cache control");
|
||||
controlThread.start();
|
||||
controlThread.join();
|
||||
|
||||
if (controlFailure.get() != null) {
|
||||
throw new AssertionError(controlFailure.get());
|
||||
}
|
||||
assertNotEquals(firstDensity, controlDensity.get(), 0D);
|
||||
assertEquals(controlDensity.get(), secondDensity, 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exactPathCarvesChunkEdgesAndRespectsWorldHeightClipping() {
|
||||
Engine engine = createEngine(48, 46);
|
||||
@@ -277,6 +316,10 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
private double sampleDensity(IrisCaveCarver3D carver, int x, int y, int z) throws Exception {
|
||||
return (double) sampleDensityMethod.invoke(carver, x, y, z);
|
||||
}
|
||||
|
||||
private long runNaiveOnce(IrisCaveCarver3D carver, int chunkX, int chunkZ, double[] columnWeights, IrisRange worldYRange, int[] precomputedSurfaceHeights, int worldHeight) throws Exception {
|
||||
WriterCapture capture = createWriterCapture(worldHeight);
|
||||
long start = System.nanoTime();
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisStructureComponentMarkerTest {
|
||||
@Test
|
||||
public void markerFilterAcceptsOnlyStorageContainers() {
|
||||
PlatformBlockState storage = mock(PlatformBlockState.class);
|
||||
PlatformBlockState solid = mock(PlatformBlockState.class);
|
||||
when(storage.isStorageChest()).thenReturn(true);
|
||||
when(solid.isStorageChest()).thenReturn(false);
|
||||
|
||||
assertTrue(IrisStructureComponent.shouldWriteStructureMarker(storage));
|
||||
assertFalse(IrisStructureComponent.shouldWriteStructureMarker(solid));
|
||||
assertFalse(IrisStructureComponent.shouldWriteStructureMarker(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void placementIdIsStableAndCoordinateSensitive() {
|
||||
int first = IrisStructureComponent.structurePlacementId("structures/village", "pieces/house", 20, -14, 35);
|
||||
int repeated = IrisStructureComponent.structurePlacementId("structures/village", "pieces/house", 20, -14, 35);
|
||||
int moved = IrisStructureComponent.structurePlacementId("structures/village", "pieces/house", 21, -14, 35);
|
||||
|
||||
assertEquals(first, repeated);
|
||||
assertNotEquals(first, moved);
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.core.nms.container.Pair;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.mantle.MantleComponent;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
import art.arcane.iris.engine.mantle.MatterGenerator;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MantleCarvingComponentBoundaryRadiusTest {
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.block(anyString())).thenReturn(block);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void boundaryWallResolutionSchedulesAdjacentCarvingChunks() {
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
MantleCarvingComponent component = new MantleCarvingComponent(engineMantle);
|
||||
|
||||
assertEquals(1, component.getRadius());
|
||||
assertEquals(1, Math.ceilDiv(component.getRadius(), 16));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void matterGenerationRunsCarvingForAllAdjacentChunks() {
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
when(dimension.isUseMantle()).thenReturn(true);
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
when(mantle.getChunk(anyInt(), anyInt())).thenReturn(chunk);
|
||||
when(chunk.use()).thenReturn(chunk);
|
||||
doAnswer(invocation -> {
|
||||
Runnable task = invocation.getArgument(1);
|
||||
task.run();
|
||||
return null;
|
||||
}).when(chunk).raiseFlagSuspend(any(), any(Runnable.class));
|
||||
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(engine.getMantle()).thenReturn(engineMantle);
|
||||
when(engineMantle.getEngine()).thenReturn(engine);
|
||||
|
||||
List<String> generatedChunks = new ArrayList<>();
|
||||
MantleCarvingComponent component = spy(new MantleCarvingComponent(engineMantle));
|
||||
doAnswer(invocation -> {
|
||||
int chunkX = invocation.getArgument(1);
|
||||
int chunkZ = invocation.getArgument(2);
|
||||
generatedChunks.add(chunkX + "," + chunkZ);
|
||||
return null;
|
||||
}).when(component).generateLayer(any(), anyInt(), anyInt(), any());
|
||||
|
||||
TestMatterGenerator generator = new TestMatterGenerator(engine, mantle, component);
|
||||
generator.generateMatter(4, -7, false, mock(ChunkContext.class));
|
||||
|
||||
assertEquals(List.of(
|
||||
"3,-8", "3,-7", "3,-6",
|
||||
"4,-8", "4,-7", "4,-6",
|
||||
"5,-8", "5,-7", "5,-6"
|
||||
), generatedChunks);
|
||||
}
|
||||
|
||||
private static final class TestMatterGenerator implements MatterGenerator {
|
||||
private final Engine engine;
|
||||
private final Mantle<Matter> mantle;
|
||||
private final List<Pair<List<MantleComponent>, Integer>> components;
|
||||
|
||||
private TestMatterGenerator(Engine engine, Mantle<Matter> mantle, MantleComponent component) {
|
||||
this.engine = engine;
|
||||
this.mantle = mantle;
|
||||
this.components = List.of(new Pair<>(List.of(component), 1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Engine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle<Matter> getMantle() {
|
||||
return mantle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRadius() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRealRadius() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<List<MantleComponent>, Integer>> getComponents() {
|
||||
return components;
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisCarveModifierInferenceIsolationTest {
|
||||
@BeforeClass
|
||||
public static void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
doReturn(block).when(registries).block(anyString());
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
doReturn(registries).when(platform).registries();
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void cavePaintingDoesNotMutateSharedBiomeInference() throws Exception {
|
||||
IrisBiome biome = new IrisBiome().setInferredType(InferredType.LAND);
|
||||
biome.getLayers().clear();
|
||||
biome.getCaveCeilingLayers().clear();
|
||||
|
||||
Engine engine = mock(Engine.class);
|
||||
doReturn(mock(IrisData.class)).when(engine).getData();
|
||||
doReturn(mock(IrisDimension.class)).when(engine).getDimension();
|
||||
doReturn(mock(IrisComplex.class)).when(engine).getComplex();
|
||||
doReturn(IrisWorld.builder().minHeight(-256).maxHeight(512).build()).when(engine).getWorld();
|
||||
IrisCarveModifier modifier = mock(IrisCarveModifier.class, CALLS_REAL_METHODS);
|
||||
doReturn(engine).when(modifier).getEngine();
|
||||
|
||||
Map<String, IrisBiome> customBiomes = new HashMap<>();
|
||||
customBiomes.put("shared", biome);
|
||||
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
|
||||
"paintBoundaryZone",
|
||||
Hunk.class,
|
||||
MatterCavern.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
int.class,
|
||||
IrisDimensionCarvingResolver.State.class,
|
||||
Long2ObjectOpenHashMap.class,
|
||||
Map.class
|
||||
);
|
||||
paintBoundaryZone.setAccessible(true);
|
||||
paintBoundaryZone.invoke(
|
||||
modifier,
|
||||
mock(Hunk.class),
|
||||
new MatterCavern(true, "shared", (byte) 0),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
new IrisDimensionCarvingResolver.State(),
|
||||
new Long2ObjectOpenHashMap<IrisBiome>(),
|
||||
customBiomes
|
||||
);
|
||||
|
||||
IrisBiome fallback = new IrisBiome();
|
||||
ProceduralStream<IrisBiome> landStream = mock(ProceduralStream.class);
|
||||
doReturn(fallback).when(landStream).get(anyDouble(), anyDouble());
|
||||
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
|
||||
Field landBiomeStream = IrisComplex.class.getDeclaredField("landBiomeStream");
|
||||
landBiomeStream.setAccessible(true);
|
||||
landBiomeStream.set(complex, landStream);
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doReturn(0D).when(region).getShoreHeight(anyDouble(), anyDouble());
|
||||
Method fixBiomeType = IrisComplex.class.getDeclaredMethod(
|
||||
"fixBiomeType",
|
||||
Double.class,
|
||||
IrisBiome.class,
|
||||
IrisRegion.class,
|
||||
Double.class,
|
||||
Double.class,
|
||||
double.class
|
||||
);
|
||||
fixBiomeType.setAccessible(true);
|
||||
IrisBiome resolved = (IrisBiome) fixBiomeType.invoke(complex, 10D, biome, region, 0D, 0D, 0D);
|
||||
assertSame(biome, resolved);
|
||||
assertEquals(InferredType.LAND, biome.getInferredType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class IrisBiomeCustomSpawnTest {
|
||||
@Test
|
||||
public void normalizesNamespacedEntityKey() {
|
||||
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType(" Minecraft:Slime ");
|
||||
|
||||
assertEquals("minecraft:slime", spawn.getTypeKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prefixesBareEntityKey() {
|
||||
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType("Slime");
|
||||
|
||||
assertEquals("minecraft:slime", spawn.getTypeKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsNullForBlankEntityKey() {
|
||||
IrisBiomeCustomSpawn spawn = new IrisBiomeCustomSpawn().setType(" ");
|
||||
|
||||
assertNull(spawn.getTypeKey());
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisBiomeGeneratorSeedIsolationTest {
|
||||
private static final double SAMPLE_X = 128D;
|
||||
private static final double SAMPLE_Z = -64D;
|
||||
|
||||
@Test
|
||||
public void engineSeedMakesPaperStyleFirstCallOrderIrrelevant() {
|
||||
IrisData data = dataWithActiveEngine(new AtomicReference<>(engine(1337L)));
|
||||
IrisBiome paperFirst = biome(data);
|
||||
IrisBiome moddedFirst = biome(data);
|
||||
|
||||
CNG paperGenerator = paperFirst.getBiomeGenerator(new RNG(8457289L));
|
||||
CNG moddedGenerator = moddedFirst.getBiomeGenerator(new RNG(-991245L));
|
||||
|
||||
assertEquals(paperGenerator.noise(SAMPLE_X, SAMPLE_Z), moddedGenerator.noise(SAMPLE_X, SAMPLE_Z), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void distinctEnginesKeepDistinctBiomeGenerators() {
|
||||
Engine firstEngine = engine(1337L);
|
||||
Engine secondEngine = engine(7331L);
|
||||
IrisBiome biome = biome(dataWithActiveEngine(new AtomicReference<>(secondEngine)));
|
||||
|
||||
CNG first = biome.getBiomeGenerator(new RNG(1L), firstEngine);
|
||||
CNG second = biome.getBiomeGenerator(new RNG(1L), secondEngine);
|
||||
|
||||
assertNotSame(first, second);
|
||||
assertNotEquals(first.noise(SAMPLE_X, SAMPLE_Z), second.noise(SAMPLE_X, SAMPLE_Z), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameSeedEnginesSharingBiomeReuseBiomeGenerator() {
|
||||
Engine firstEngine = engine(1337L);
|
||||
Engine secondEngine = engine(1337L);
|
||||
IrisBiome biome = biome(dataWithActiveEngine(new AtomicReference<>(secondEngine)));
|
||||
|
||||
CNG first = biome.getBiomeGenerator(new RNG(11L), firstEngine);
|
||||
CNG second = biome.getBiomeGenerator(new RNG(99L), secondEngine);
|
||||
|
||||
assertSame(first, second);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void engineLessToolingUsesSuppliedRngSeed() {
|
||||
IrisBiome biome = biome(null);
|
||||
|
||||
CNG first = biome.getBiomeGenerator(new RNG(11L));
|
||||
CNG sameSeed = biome.getBiomeGenerator(new RNG(11L));
|
||||
CNG second = biome.getBiomeGenerator(new RNG(99L));
|
||||
|
||||
assertSame(first, sameSeed);
|
||||
assertNotSame(first, second);
|
||||
assertNotEquals(first.noise(SAMPLE_X, SAMPLE_Z), second.noise(SAMPLE_X, SAMPLE_Z), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentExactEnginesIgnoreLoaderEngine() throws Exception {
|
||||
Engine firstEngine = engine(1337L);
|
||||
Engine secondEngine = engine(7331L);
|
||||
IrisBiome biome = biome(dataWithActiveEngine(new AtomicReference<>(secondEngine)));
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<CNG> firstFuture = executor.submit(() -> repeatedlyResolve(biome, firstEngine, start));
|
||||
Future<CNG> secondFuture = executor.submit(() -> repeatedlyResolve(biome, secondEngine, start));
|
||||
start.countDown();
|
||||
|
||||
CNG first = firstFuture.get();
|
||||
CNG second = secondFuture.get();
|
||||
assertNotSame(first, second);
|
||||
assertSame(first, biome.getBiomeGenerator(new RNG(99L), firstEngine));
|
||||
assertSame(second, biome.getBiomeGenerator(new RNG(99L), secondEngine));
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biomeGeneratorCacheEvictsOldSeeds() {
|
||||
IrisBiome biome = biome(null);
|
||||
CNG first = biome.getBiomeGenerator(new RNG(0L));
|
||||
|
||||
for (long seed = 1L; seed <= 8L; seed++) {
|
||||
biome.getBiomeGenerator(new RNG(seed));
|
||||
}
|
||||
|
||||
assertNotSame(first, biome.getBiomeGenerator(new RNG(0L)));
|
||||
}
|
||||
|
||||
private IrisBiome biome(IrisData data) {
|
||||
IrisBiome biome = new IrisBiome().setName("Scatter Test");
|
||||
biome.setLoader(data);
|
||||
return biome;
|
||||
}
|
||||
|
||||
private IrisData dataWithActiveEngine(AtomicReference<Engine> activeEngine) {
|
||||
IrisData data = mock(IrisData.class);
|
||||
when(data.getEngine()).thenAnswer(ignored -> activeEngine.get());
|
||||
return data;
|
||||
}
|
||||
|
||||
private Engine engine(long biomeSeed) {
|
||||
SeedManager seedManager = mock(SeedManager.class);
|
||||
when(seedManager.getBiome()).thenReturn(biomeSeed);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getSeedManager()).thenReturn(seedManager);
|
||||
return engine;
|
||||
}
|
||||
|
||||
private CNG repeatedlyResolve(IrisBiome biome, Engine engine, CountDownLatch start) throws InterruptedException {
|
||||
start.await();
|
||||
CNG generator = biome.getBiomeGenerator(new RNG(1L), engine);
|
||||
for (int index = 0; index < 100; index++) {
|
||||
assertSame(generator, biome.getBiomeGenerator(new RNG(index + 2L), engine));
|
||||
}
|
||||
return generator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IrisDimensionBiomeTagTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void biomeTagWritesAreSortedAndDeduplicated() throws Exception {
|
||||
Path output = temporaryFolder.getRoot().toPath().resolve("allows_surface_slime_spawns.json");
|
||||
|
||||
IrisDimension.writeBiomeTag(output, "overworld:swamp_b");
|
||||
IrisDimension.writeBiomeTag(output, "overworld:swamp_a");
|
||||
IrisDimension.writeBiomeTag(output, "overworld:swamp_b");
|
||||
|
||||
JSONObject tag = new JSONObject(Files.readString(output, StandardCharsets.UTF_8));
|
||||
JSONArray values = tag.getJSONArray("values");
|
||||
assertFalse(tag.getBoolean("replace"));
|
||||
assertEquals(2, values.length());
|
||||
assertEquals("overworld:swamp_a", values.getString(0));
|
||||
assertEquals("overworld:swamp_b", values.getString(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customBiomeTagUsesTheMinecraftBiomeTagPath() throws Exception {
|
||||
KList<String> tags = new KList<>();
|
||||
tags.add("minecraft:allows_surface_slime_spawns");
|
||||
IrisDimension.installBiomeTags(temporaryFolder.getRoot(), "overworld:swamp", tags);
|
||||
|
||||
Path output = temporaryFolder.getRoot().toPath()
|
||||
.resolve("iris/data/minecraft/tags/worldgen/biome/allows_surface_slime_spawns.json");
|
||||
JSONObject tag = new JSONObject(Files.readString(output, StandardCharsets.UTF_8));
|
||||
|
||||
assertEquals("overworld:swamp", tag.getJSONArray("values").getString(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisStructureLootPlacementTest {
|
||||
@Test
|
||||
public void createsPiecePlacementWithAuthoredLootOrderAndDefaultWeights() {
|
||||
IrisStructure structure = new IrisStructure();
|
||||
structure.getLoot().add("village_common");
|
||||
structure.getLoot().add("village_rare");
|
||||
|
||||
IrisObjectPlacement placement = structure.createLootPlacement("pieces/village_house");
|
||||
|
||||
assertEquals(1, placement.getPlace().size());
|
||||
assertEquals("pieces/village_house", placement.getPlace().get(0));
|
||||
assertEquals(2, placement.getLoot().size());
|
||||
assertEquals("village_common", placement.getLoot().get(0).getName());
|
||||
assertEquals(1, placement.getLoot().get(0).getWeight());
|
||||
assertTrue(placement.getLoot().get(0).getFilter().isEmpty());
|
||||
assertEquals("village_rare", placement.getLoot().get(1).getName());
|
||||
assertEquals(1, placement.getLoot().get(1).getWeight());
|
||||
assertTrue(placement.getLoot().get(1).getFilter().isEmpty());
|
||||
assertFalse(placement.isOverrideGlobalLoot());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsEmptyLootPlacementWhenStructureHasNoLoot() {
|
||||
IrisObjectPlacement placement = new IrisStructure().createLootPlacement("pieces/empty_house");
|
||||
|
||||
assertEquals("pieces/empty_house", placement.getPlace().get(0));
|
||||
assertTrue(placement.getLoot().isEmpty());
|
||||
assertFalse(placement.isOverrideGlobalLoot());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user