mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
Vwoop
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
import art.arcane.iris.engine.IrisEngine;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedComponent;
|
||||
@@ -122,7 +124,7 @@ public final class GenerationProbe {
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
IrisPlatforms.bind(new StubPlatform());
|
||||
StubPlatform.bindGenerationStateHandlers();
|
||||
StubPlatform.verbose(true);
|
||||
StubPlatform.errorSink(REPORTED::add);
|
||||
IrisServices.register(PreservationRegistry.class, new InertPreservation());
|
||||
@@ -132,6 +134,8 @@ public final class GenerationProbe {
|
||||
|
||||
File packSource = new File(args[0]);
|
||||
int radius = args.length > 1 ? Integer.parseInt(args[1]) : 2;
|
||||
int centerChunkX = args.length > 2 ? Integer.parseInt(args[2]) : 0;
|
||||
int centerChunkZ = args.length > 3 ? Integer.parseInt(args[3]) : 0;
|
||||
if (!packSource.isDirectory()) {
|
||||
System.out.println("[genprobe] pack folder not found: " + packSource.getAbsolutePath());
|
||||
System.exit(1);
|
||||
@@ -141,8 +145,25 @@ public final class GenerationProbe {
|
||||
File pack = clonePack(packSource, workRoot);
|
||||
System.out.println("[genprobe] pack: " + packSource.getAbsolutePath());
|
||||
System.out.println("[genprobe] work copy: " + pack.getAbsolutePath());
|
||||
System.out.println("[genprobe] center chunk: " + centerChunkX + "," + centerChunkZ);
|
||||
System.out.println("[genprobe] radius: " + radius + " (" + ((2 * radius + 1) * (2 * radius + 1)) + " chunks)");
|
||||
|
||||
PackValidationResult validation = PackValidator.validate(pack);
|
||||
for (String warning : validation.getWarnings()) {
|
||||
System.out.println("[genprobe] pack warning: " + warning);
|
||||
}
|
||||
if (!validation.isLoadable()) {
|
||||
for (String error : validation.getBlockingErrors()) {
|
||||
System.out.println("[genprobe] pack error: " + error);
|
||||
}
|
||||
System.out.println("[genprobe] FAIL: pack validation blocked generation");
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
System.out.println("[genprobe] pack validation: PASS");
|
||||
|
||||
IrisPlatforms.bind(new StubPlatform());
|
||||
|
||||
Engine engine;
|
||||
try {
|
||||
IrisData data = IrisData.get(pack);
|
||||
@@ -184,8 +205,8 @@ public final class GenerationProbe {
|
||||
int ok = 0;
|
||||
int failed = 0;
|
||||
|
||||
for (int cz = -radius; cz <= radius; cz++) {
|
||||
for (int cx = -radius; cx <= radius; cx++) {
|
||||
for (int cz = centerChunkZ - radius; cz <= centerChunkZ + radius; cz++) {
|
||||
for (int cx = centerChunkX - radius; cx <= centerChunkX + radius; cx++) {
|
||||
String at = cx + "," + cz;
|
||||
drainReported();
|
||||
List<Throwable> failures = new ArrayList<>();
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.engine.object.BlockDataMergeSupport;
|
||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.LogLevel;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
@@ -30,9 +33,12 @@ import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.spi.PlatformScheduler;
|
||||
import art.arcane.iris.spi.PlatformStructureHooks;
|
||||
import art.arcane.iris.spi.PlatformWorld;
|
||||
import art.arcane.iris.util.common.math.IrisBlockVector;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
@@ -49,6 +55,25 @@ public final class StubPlatform implements IrisPlatform {
|
||||
ERROR_SINK = sink;
|
||||
}
|
||||
|
||||
public static void bindGenerationStateHandlers() {
|
||||
IrisObjectRotation.bindPlatformRotator(StubPlatform::rotateState);
|
||||
BlockDataMergeSupport.bindPlatformMerger(StubPlatform::mergeStates);
|
||||
TileData.bindPlatformReader(StubTileData::read);
|
||||
TileData.bindPlatformFactory(StubTileData::fromProperties);
|
||||
}
|
||||
|
||||
static PlatformBlockState rotateForTest(IrisObjectRotation rotation, PlatformBlockState state) {
|
||||
return rotateState(rotation, state, 0, 0, 0);
|
||||
}
|
||||
|
||||
static PlatformBlockState mergeForTest(PlatformBlockState base, PlatformBlockState update) {
|
||||
return mergeStates(base, update);
|
||||
}
|
||||
|
||||
static PlatformBlockState blockStateForTest(String key) {
|
||||
return StubBlockState.of(key);
|
||||
}
|
||||
|
||||
private final StubRegistries registries = new StubRegistries();
|
||||
private final StubScheduler scheduler = new StubScheduler();
|
||||
private final StubStructureHooks structureHooks = new StubStructureHooks();
|
||||
@@ -193,7 +218,9 @@ public final class StubPlatform implements IrisPlatform {
|
||||
|
||||
@Override
|
||||
public PlatformBlockState withProperty(String name, String value) {
|
||||
return this;
|
||||
ParsedState parsed = ParsedState.parse(key);
|
||||
parsed.properties().put(normalizeProperty(name), normalizeProperty(value));
|
||||
return of(parsed.serialize());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,6 +229,184 @@ public final class StubPlatform implements IrisPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
private static PlatformBlockState rotateState(IrisObjectRotation rotation, PlatformBlockState state,
|
||||
int spinX, int spinY, int spinZ) {
|
||||
if (state == null || rotation == null || !rotation.canRotate()) {
|
||||
return state;
|
||||
}
|
||||
ParsedState parsed = ParsedState.parse(state.key());
|
||||
Map<String, String> properties = parsed.properties();
|
||||
if (properties.containsKey("facing")) {
|
||||
properties.put("facing", rotateFace(rotation, properties.get("facing"), spinX, spinY, spinZ));
|
||||
} else if (properties.containsKey("rotation")) {
|
||||
properties.put("rotation", rotateSegment(rotation, properties.get("rotation"), spinX, spinY, spinZ));
|
||||
} else if (properties.containsKey("axis")) {
|
||||
properties.put("axis", rotateAxis(rotation, properties.get("axis"), spinX, spinY, spinZ));
|
||||
} else {
|
||||
rotateFaceProperties(rotation, properties, spinX, spinY, spinZ);
|
||||
}
|
||||
return StubBlockState.of(parsed.serialize());
|
||||
}
|
||||
|
||||
private static PlatformBlockState mergeStates(PlatformBlockState base, PlatformBlockState update) {
|
||||
if (base == null) {
|
||||
return update;
|
||||
}
|
||||
if (update == null) {
|
||||
return base;
|
||||
}
|
||||
ParsedState parsedBase = ParsedState.parse(base.key());
|
||||
ParsedState parsedUpdate = ParsedState.parse(update.key());
|
||||
if (!parsedBase.blockKey().equals(parsedUpdate.blockKey())) {
|
||||
return update;
|
||||
}
|
||||
parsedBase.properties().putAll(parsedUpdate.properties());
|
||||
return StubBlockState.of(parsedBase.serialize());
|
||||
}
|
||||
|
||||
private static String rotateFace(IrisObjectRotation rotation, String face,
|
||||
int spinX, int spinY, int spinZ) {
|
||||
IrisBlockVector vector = faceVector(face);
|
||||
if (vector == null) {
|
||||
return face;
|
||||
}
|
||||
return faceName(rotation.rotate(vector, spinX, spinY, spinZ));
|
||||
}
|
||||
|
||||
private static String rotateAxis(IrisObjectRotation rotation, String axis,
|
||||
int spinX, int spinY, int spinZ) {
|
||||
IrisBlockVector vector = switch (axis) {
|
||||
case "x" -> new IrisBlockVector(1, 0, 0);
|
||||
case "y" -> new IrisBlockVector(0, 1, 0);
|
||||
case "z" -> new IrisBlockVector(0, 0, 1);
|
||||
default -> null;
|
||||
};
|
||||
if (vector == null) {
|
||||
return axis;
|
||||
}
|
||||
IrisBlockVector rotated = rotation.rotate(vector, spinX, spinY, spinZ);
|
||||
double x = Math.abs(rotated.getX());
|
||||
double y = Math.abs(rotated.getY());
|
||||
double z = Math.abs(rotated.getZ());
|
||||
if (x >= y && x >= z) {
|
||||
return "x";
|
||||
}
|
||||
return y >= z ? "y" : "z";
|
||||
}
|
||||
|
||||
private static String rotateSegment(IrisObjectRotation rotation, String value,
|
||||
int spinX, int spinY, int spinZ) {
|
||||
int segment;
|
||||
try {
|
||||
segment = Math.floorMod(Integer.parseInt(value), 16);
|
||||
} catch (NumberFormatException e) {
|
||||
return value;
|
||||
}
|
||||
double angle = segment * Math.PI * 2D / 16D;
|
||||
IrisBlockVector vector = new IrisBlockVector(-Math.sin(angle), 0D, Math.cos(angle));
|
||||
IrisBlockVector rotated = rotation.rotate(vector, spinX, spinY, spinZ);
|
||||
if (Math.abs(rotated.getY()) > Math.max(Math.abs(rotated.getX()), Math.abs(rotated.getZ()))) {
|
||||
return value;
|
||||
}
|
||||
double rotatedAngle = Math.atan2(-rotated.getX(), rotated.getZ());
|
||||
int rotatedSegment = (int) Math.round(rotatedAngle * 16D / (Math.PI * 2D));
|
||||
return Integer.toString(Math.floorMod(rotatedSegment, 16));
|
||||
}
|
||||
|
||||
private static void rotateFaceProperties(IrisObjectRotation rotation, Map<String, String> properties,
|
||||
int spinX, int spinY, int spinZ) {
|
||||
List<String> faces = List.of("north", "east", "south", "west", "up", "down");
|
||||
int present = 0;
|
||||
for (String face : faces) {
|
||||
if (properties.containsKey(face)) {
|
||||
present++;
|
||||
}
|
||||
}
|
||||
if (present < 2) {
|
||||
return;
|
||||
}
|
||||
Map<String, String> rotated = new LinkedHashMap<>();
|
||||
for (String face : faces) {
|
||||
String value = properties.get(face);
|
||||
if (value != null) {
|
||||
rotated.put(rotateFace(rotation, face, spinX, spinY, spinZ), value);
|
||||
}
|
||||
}
|
||||
for (String face : faces) {
|
||||
if (properties.containsKey(face)) {
|
||||
String defaultValue = "true".equals(properties.get(face)) || "false".equals(properties.get(face))
|
||||
? "false" : "none";
|
||||
properties.put(face, rotated.getOrDefault(face, defaultValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisBlockVector faceVector(String face) {
|
||||
return switch (face) {
|
||||
case "north" -> new IrisBlockVector(0, 0, -1);
|
||||
case "east" -> new IrisBlockVector(1, 0, 0);
|
||||
case "south" -> new IrisBlockVector(0, 0, 1);
|
||||
case "west" -> new IrisBlockVector(-1, 0, 0);
|
||||
case "up" -> new IrisBlockVector(0, 1, 0);
|
||||
case "down" -> new IrisBlockVector(0, -1, 0);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private static String faceName(IrisBlockVector vector) {
|
||||
double x = Math.abs(vector.getX());
|
||||
double y = Math.abs(vector.getY());
|
||||
double z = Math.abs(vector.getZ());
|
||||
if (x >= y && x >= z) {
|
||||
return vector.getX() >= 0D ? "east" : "west";
|
||||
}
|
||||
if (y >= z) {
|
||||
return vector.getY() >= 0D ? "up" : "down";
|
||||
}
|
||||
return vector.getZ() >= 0D ? "south" : "north";
|
||||
}
|
||||
|
||||
private static String normalizeProperty(String value) {
|
||||
return value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private record ParsedState(String blockKey, LinkedHashMap<String, String> properties) {
|
||||
private static ParsedState parse(String key) {
|
||||
String normalized = key == null ? "minecraft:air" : key.trim().toLowerCase(Locale.ROOT);
|
||||
int open = normalized.indexOf('[');
|
||||
if (open < 0 || !normalized.endsWith("]")) {
|
||||
return new ParsedState(normalized, new LinkedHashMap<>());
|
||||
}
|
||||
LinkedHashMap<String, String> properties = new LinkedHashMap<>();
|
||||
String body = normalized.substring(open + 1, normalized.length() - 1);
|
||||
if (!body.isBlank()) {
|
||||
for (String property : body.split(",")) {
|
||||
int separator = property.indexOf('=');
|
||||
if (separator > 0 && separator < property.length() - 1) {
|
||||
properties.put(property.substring(0, separator), property.substring(separator + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ParsedState(normalized.substring(0, open), properties);
|
||||
}
|
||||
|
||||
private String serialize() {
|
||||
if (properties.isEmpty()) {
|
||||
return blockKey;
|
||||
}
|
||||
StringBuilder serialized = new StringBuilder(blockKey).append('[');
|
||||
boolean first = true;
|
||||
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||
if (!first) {
|
||||
serialized.append(',');
|
||||
}
|
||||
serialized.append(entry.getKey()).append('=').append(entry.getValue());
|
||||
first = false;
|
||||
}
|
||||
return serialized.append(']').toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StubBiome implements PlatformBiome {
|
||||
private static final ConcurrentHashMap<String, StubBiome> CACHE = new ConcurrentHashMap<>();
|
||||
private final String key;
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.Strictness;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public final class StubTileData extends TileData {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.setStrictness(Strictness.LENIENT)
|
||||
.create();
|
||||
|
||||
private final String blockKey;
|
||||
private final KMap<String, Object> tileProperties;
|
||||
private final byte[] binary;
|
||||
|
||||
private StubTileData(String blockKey, KMap<String, Object> tileProperties, byte[] binary) {
|
||||
super();
|
||||
this.blockKey = normalizeBlockKey(blockKey);
|
||||
this.tileProperties = tileProperties.copy();
|
||||
this.binary = Arrays.copyOf(binary, binary.length);
|
||||
}
|
||||
|
||||
public static StubTileData read(DataInputStream input) throws IOException {
|
||||
if (!input.markSupported()) {
|
||||
throw new IOException("Probe tile-data input does not support mark/reset");
|
||||
}
|
||||
input.mark(Integer.MAX_VALUE);
|
||||
try {
|
||||
return readModern(input);
|
||||
} catch (IOException | RuntimeException error) {
|
||||
input.reset();
|
||||
return readLegacy(input);
|
||||
} finally {
|
||||
input.mark(0);
|
||||
}
|
||||
}
|
||||
|
||||
static StubTileData fromProperties(PlatformBlockState state, KMap<String, Object> properties) {
|
||||
if (state == null) {
|
||||
throw new IllegalArgumentException("Probe tile data requires a block state");
|
||||
}
|
||||
String blockKey = normalizeBlockKey(state.placementBaseState().key());
|
||||
KMap<String, Object> copied = properties == null ? new KMap<>() : properties.copy();
|
||||
return new StubTileData(blockKey, copied, encodeModern(blockKey, copied));
|
||||
}
|
||||
|
||||
private static StubTileData readModern(DataInputStream input) throws IOException {
|
||||
String blockKey = input.readUTF();
|
||||
if (!isResourceKey(blockKey)) {
|
||||
throw new IOException("Probe tile data has an invalid block key: " + blockKey);
|
||||
}
|
||||
KMap<String, Object> properties = properties(input.readUTF());
|
||||
if (properties == null) {
|
||||
throw new IOException("Probe tile data has invalid properties for " + blockKey);
|
||||
}
|
||||
return new StubTileData(blockKey, properties, encodeModern(blockKey, properties));
|
||||
}
|
||||
|
||||
private static StubTileData readLegacy(DataInputStream input) throws IOException {
|
||||
int type = input.readUnsignedShort();
|
||||
return switch (type) {
|
||||
case 0 -> readLegacySign(input, type);
|
||||
case 1 -> readLegacySpawner(input, type);
|
||||
case 2 -> readLegacyBanner(input, type);
|
||||
case 3 -> readLegacyLootable(input, type);
|
||||
default -> throw new IOException("Unknown probe tile-data type: " + type);
|
||||
};
|
||||
}
|
||||
|
||||
private static StubTileData readLegacySign(DataInputStream input, int type) throws IOException {
|
||||
List<String> lines = List.of(
|
||||
input.readUTF(), input.readUTF(), input.readUTF(), input.readUTF());
|
||||
int color = input.readUnsignedByte();
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("lines", lines);
|
||||
properties.put("color", color);
|
||||
byte[] binary = encode(output -> {
|
||||
output.writeShort(type);
|
||||
for (String line : lines) {
|
||||
output.writeUTF(line);
|
||||
}
|
||||
output.writeByte(color);
|
||||
});
|
||||
return new StubTileData("minecraft:oak_sign", properties, binary);
|
||||
}
|
||||
|
||||
private static StubTileData readLegacySpawner(DataInputStream input, int type) throws IOException {
|
||||
input.mark(Integer.MAX_VALUE);
|
||||
String entityKey = null;
|
||||
try {
|
||||
String candidate = input.readUTF();
|
||||
if (isResourceKey(candidate)) {
|
||||
entityKey = candidate;
|
||||
} else {
|
||||
input.reset();
|
||||
}
|
||||
} catch (IOException | RuntimeException error) {
|
||||
input.reset();
|
||||
}
|
||||
int legacyOrdinal = -1;
|
||||
if (entityKey == null) {
|
||||
legacyOrdinal = input.readShort();
|
||||
}
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("entity", entityKey == null ? legacyOrdinal : entityKey);
|
||||
String resolvedEntityKey = entityKey;
|
||||
int resolvedLegacyOrdinal = legacyOrdinal;
|
||||
byte[] binary = encode(output -> {
|
||||
output.writeShort(type);
|
||||
if (resolvedEntityKey == null) {
|
||||
output.writeShort(resolvedLegacyOrdinal);
|
||||
} else {
|
||||
output.writeUTF(resolvedEntityKey);
|
||||
}
|
||||
});
|
||||
return new StubTileData("minecraft:spawner", properties, binary);
|
||||
}
|
||||
|
||||
private static StubTileData readLegacyBanner(DataInputStream input, int type) throws IOException {
|
||||
int baseColor = input.readUnsignedByte();
|
||||
int patternCount = input.readUnsignedByte();
|
||||
input.mark(Integer.MAX_VALUE);
|
||||
List<BannerPattern> patterns = new ArrayList<>(patternCount);
|
||||
boolean keyed = true;
|
||||
try {
|
||||
for (int i = 0; i < patternCount; i++) {
|
||||
int color = input.readUnsignedByte();
|
||||
String pattern = input.readUTF();
|
||||
if (!isResourceKey(pattern)) {
|
||||
throw new IOException("Invalid probe banner pattern: " + pattern);
|
||||
}
|
||||
patterns.add(new BannerPattern(color, pattern, -1));
|
||||
}
|
||||
} catch (IOException | RuntimeException error) {
|
||||
keyed = false;
|
||||
patterns.clear();
|
||||
input.reset();
|
||||
for (int i = 0; i < patternCount; i++) {
|
||||
patterns.add(new BannerPattern(input.readUnsignedByte(), null, input.readUnsignedByte()));
|
||||
}
|
||||
}
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("baseColor", baseColor);
|
||||
properties.put("patterns", patterns);
|
||||
boolean resolvedKeyed = keyed;
|
||||
byte[] binary = encode(output -> {
|
||||
output.writeShort(type);
|
||||
output.writeByte(baseColor);
|
||||
output.writeByte(patternCount);
|
||||
for (BannerPattern pattern : patterns) {
|
||||
output.writeByte(pattern.color());
|
||||
if (resolvedKeyed) {
|
||||
output.writeUTF(pattern.key());
|
||||
} else {
|
||||
output.writeByte(pattern.legacyOrdinal());
|
||||
}
|
||||
}
|
||||
});
|
||||
return new StubTileData("minecraft:white_banner", properties, binary);
|
||||
}
|
||||
|
||||
private static StubTileData readLegacyLootable(DataInputStream input, int type) throws IOException {
|
||||
String blockKey = input.readUTF();
|
||||
String lootTable = input.readUTF();
|
||||
long seed = input.readLong();
|
||||
if (!isResourceKey(blockKey)) {
|
||||
throw new IOException("Probe lootable tile has an invalid block key: " + blockKey);
|
||||
}
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("LootTable", lootTable);
|
||||
properties.put("LootTableSeed", seed);
|
||||
byte[] binary = encode(output -> {
|
||||
output.writeShort(type);
|
||||
output.writeUTF(blockKey);
|
||||
output.writeUTF(lootTable);
|
||||
output.writeLong(seed);
|
||||
});
|
||||
return new StubTileData(blockKey, properties, binary);
|
||||
}
|
||||
|
||||
private static byte[] encodeModern(String blockKey, KMap<String, Object> properties) {
|
||||
try {
|
||||
return encode(output -> {
|
||||
output.writeUTF(blockKey);
|
||||
output.writeUTF(GSON.toJson(properties));
|
||||
});
|
||||
} catch (IOException error) {
|
||||
throw new IllegalStateException("Failed to encode probe tile data for " + blockKey, error);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] encode(BinaryWriter writer) throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (DataOutputStream output = new DataOutputStream(bytes)) {
|
||||
writer.write(output);
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static KMap<String, Object> properties(String json) {
|
||||
return GSON.fromJson(json, KMap.class);
|
||||
}
|
||||
|
||||
private static boolean isResourceKey(String key) {
|
||||
if (key == null || key.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
return key.matches("[a-z0-9_.-]+:[a-z0-9/._-]+");
|
||||
}
|
||||
|
||||
private static String normalizeBlockKey(String blockKey) {
|
||||
int properties = blockKey.indexOf('[');
|
||||
return properties < 0 ? blockKey : blockKey.substring(0, properties);
|
||||
}
|
||||
|
||||
String blockKey() {
|
||||
return blockKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KMap<String, Object> getProperties() {
|
||||
return tileProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBinary(DataOutputStream output) throws IOException {
|
||||
output.write(binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TileData clone() {
|
||||
return new StubTileData(blockKey, tileProperties, binary);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return blockKey + GSON.toJson(tileProperties);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BinaryWriter {
|
||||
void write(DataOutputStream output) throws IOException;
|
||||
}
|
||||
|
||||
private record BannerPattern(int color, String key, int legacyOrdinal) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisObjectRotation;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class StubPlatformStateTest {
|
||||
@BeforeClass
|
||||
public static void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
IrisPlatforms.bind(new StubPlatform());
|
||||
StubPlatform.bindGenerationStateHandlers();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rotatesFacingAxisAndConnectedFaces() {
|
||||
IrisObjectRotation rotation = IrisObjectRotation.of(0, 90, 0);
|
||||
|
||||
assertEquals("minecraft:oak_stairs[facing=west,half=bottom]",
|
||||
StubPlatform.rotateForTest(rotation, state("minecraft:oak_stairs[facing=north,half=bottom]")).key());
|
||||
assertEquals("minecraft:oak_log[axis=x]",
|
||||
StubPlatform.rotateForTest(rotation, state("minecraft:oak_log[axis=z]")).key());
|
||||
assertEquals("minecraft:oak_fence[north=false,east=false,south=false,west=true]",
|
||||
StubPlatform.rotateForTest(rotation,
|
||||
state("minecraft:oak_fence[north=true,east=false,south=false,west=false]")).key());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertyUpdatesAndMergesPreserveCanonicalState() {
|
||||
PlatformBlockState base = state("minecraft:oak_leaves[distance=7,persistent=false]");
|
||||
PlatformBlockState updated = base.withProperty("distance", "2").withProperty("persistent", "true");
|
||||
|
||||
assertEquals("minecraft:oak_leaves[distance=2,persistent=true]", updated.key());
|
||||
|
||||
PlatformBlockState merged = StubPlatform.mergeForTest(
|
||||
base, state("minecraft:oak_leaves[persistent=true]"));
|
||||
assertEquals("minecraft:oak_leaves[distance=7,persistent=true]", merged.key());
|
||||
}
|
||||
|
||||
private PlatformBlockState state(String key) {
|
||||
return IrisPlatforms.get().registries().block(key);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class StubTileDataTest {
|
||||
@Test
|
||||
public void modernTilePayloadRoundTripsWithoutBukkit() throws Exception {
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("LootTable", "minecraft:chests/ancient_city");
|
||||
StubTileData original = StubTileData.fromProperties(
|
||||
StubPlatform.blockStateForTest("minecraft:chest[facing=north]"), properties);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (DataOutputStream output = new DataOutputStream(bytes)) {
|
||||
original.toBinary(output);
|
||||
}
|
||||
|
||||
StubTileData decoded;
|
||||
try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
|
||||
decoded = StubTileData.read(input);
|
||||
}
|
||||
|
||||
assertEquals("minecraft:chest", decoded.blockKey());
|
||||
assertEquals("minecraft:chests/ancient_city", decoded.getProperties().get("LootTable"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyLootablePayloadPreservesItsCompleteFrame() throws Exception {
|
||||
byte[] payload = encode(output -> {
|
||||
output.writeShort(3);
|
||||
output.writeUTF("minecraft:chest");
|
||||
output.writeUTF("minecraft:chests/ancient_city");
|
||||
output.writeLong(998877L);
|
||||
});
|
||||
|
||||
StubTileData decoded = decode(payload);
|
||||
|
||||
assertEquals("minecraft:chest", decoded.blockKey());
|
||||
assertEquals("minecraft:chests/ancient_city", decoded.getProperties().get("LootTable"));
|
||||
assertArrayEquals(payload, encode(decoded::toBinary));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacySpawnerAndBannerFramesRemainUnambiguous() throws Exception {
|
||||
byte[] ordinalSpawner = encode(output -> {
|
||||
output.writeShort(1);
|
||||
output.writeShort(42);
|
||||
});
|
||||
byte[] keyedBanner = encode(output -> {
|
||||
output.writeShort(2);
|
||||
output.writeByte(11);
|
||||
output.writeByte(1);
|
||||
output.writeByte(3);
|
||||
output.writeUTF("minecraft:base");
|
||||
});
|
||||
|
||||
assertArrayEquals(ordinalSpawner, encode(decode(ordinalSpawner)::toBinary));
|
||||
assertArrayEquals(keyedBanner, encode(decode(keyedBanner)::toBinary));
|
||||
}
|
||||
|
||||
private static StubTileData decode(byte[] payload) throws Exception {
|
||||
try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(payload))) {
|
||||
return StubTileData.read(input);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] encode(BinaryWriter writer) throws Exception {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
try (DataOutputStream output = new DataOutputStream(bytes)) {
|
||||
writer.write(output);
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BinaryWriter {
|
||||
void write(DataOutputStream output) throws Exception;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user