diff --git a/core/build.gradle b/core/build.gradle index bed15cb73..eb7053511 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -61,6 +61,8 @@ boolean hasSentryAuthToken = sentryAuthToken != null && !sentryAuthToken.isBlank * these dependencies if they are available on mvn central. */ dependencies { + api(project(':spi')) + // Provided or Classpath compileOnly(libs.spigot) compileOnly(libs.log4j.api) diff --git a/core/src/main/java/art/arcane/iris/Iris.java b/core/src/main/java/art/arcane/iris/Iris.java index 6859f10fa..1c10df9cf 100644 --- a/core/src/main/java/art/arcane/iris/Iris.java +++ b/core/src/main/java/art/arcane/iris/Iris.java @@ -49,6 +49,8 @@ import art.arcane.iris.engine.object.IrisWorld; import art.arcane.iris.engine.platform.BukkitChunkGenerator; import art.arcane.iris.core.safeguard.IrisSafeguard; import art.arcane.iris.engine.platform.PlatformChunkGenerator; +import art.arcane.iris.platform.bukkit.BukkitPlatform; +import art.arcane.iris.spi.IrisPlatforms; import art.arcane.volmlib.integration.ReloadAware; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; @@ -911,6 +913,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware { } public void onEnable() { + IrisPlatforms.bind(new BukkitPlatform()); enable(); super.onEnable(); Bukkit.getPluginManager().registerEvents(this, this); diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java new file mode 100644 index 000000000..245ffe4a6 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBiome.java @@ -0,0 +1,75 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.PlatformBiome; +import org.bukkit.block.Biome; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Interned Bukkit adapter for a neutral biome handle. + */ +public final class BukkitBiome implements PlatformBiome { + private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + + private final Biome biome; + private final String key; + private final String namespace; + + private BukkitBiome(Biome biome, String key) { + this.biome = biome; + this.key = key; + int colon = key.indexOf(':'); + this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft"; + } + + public static BukkitBiome of(Biome biome) { + String key = biomeKey(biome); + return CACHE.computeIfAbsent(key, (String k) -> new BukkitBiome(biome, k)); + } + + private static String biomeKey(Biome biome) { + for (String method : new String[]{"getKeyOrNull", "getKeyOrThrow", "getKey"}) { + try { + Object key = Biome.class.getMethod(method).invoke(biome); + if (key != null) { + return key.toString(); + } + } catch (Throwable ignored) { + } + } + return biome.toString(); + } + + @Override + public String key() { + return key; + } + + @Override + public String namespace() { + return namespace; + } + + @Override + public Object nativeHandle() { + return biome; + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java new file mode 100644 index 000000000..428b1427b --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitBlockState.java @@ -0,0 +1,126 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.core.nms.INMS; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.common.data.B; +import org.bukkit.Bukkit; +import org.bukkit.block.data.BlockData; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Interned Bukkit adapter for a neutral block state backed by BlockData. + */ +public final class BukkitBlockState implements PlatformBlockState { + private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + + private final BlockData data; + private final String key; + private final String namespace; + + private BukkitBlockState(BlockData data, String key) { + this.data = data; + this.key = key; + this.namespace = parseNamespace(key); + } + + public static BukkitBlockState of(BlockData data) { + String key = data.getAsString(); + return CACHE.computeIfAbsent(key, (String k) -> new BukkitBlockState(data, k)); + } + + private static String parseNamespace(String key) { + String base = key; + int bracket = base.indexOf('['); + if (bracket >= 0) { + base = base.substring(0, bracket); + } + int colon = base.indexOf(':'); + return colon >= 0 ? base.substring(0, colon) : "minecraft"; + } + + private static String mergeProperty(String key, String name, String value) { + int bracket = key.indexOf('['); + if (bracket < 0) { + return key + "[" + name + "=" + value + "]"; + } + String base = key.substring(0, bracket); + String body = key.substring(bracket + 1, key.lastIndexOf(']')); + LinkedHashMap properties = new LinkedHashMap<>(); + for (String entry : body.split(",")) { + int equals = entry.indexOf('='); + if (equals < 0) { + continue; + } + properties.put(entry.substring(0, equals).trim(), entry.substring(equals + 1).trim()); + } + properties.put(name, value); + StringBuilder merged = new StringBuilder(base).append('['); + boolean first = true; + for (Map.Entry property : properties.entrySet()) { + if (!first) { + merged.append(','); + } + merged.append(property.getKey()).append('=').append(property.getValue()); + first = false; + } + return merged.append(']').toString(); + } + + @Override + public String key() { + return key; + } + + @Override + public String namespace() { + return namespace; + } + + @Override + public boolean isAir() { + return B.isAir(data); + } + + @Override + public boolean isSolid() { + return B.isSolid(data); + } + + @Override + public boolean hasTileEntity() { + return INMS.get().hasTile(data.getMaterial()); + } + + @Override + public PlatformBlockState withProperty(String name, String value) { + String merged = mergeProperty(key, name, value); + BlockData resolved = Bukkit.createBlockData(merged); + return of(resolved); + } + + @Override + public Object nativeHandle() { + return data; + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java new file mode 100644 index 000000000..3b33890df --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitChunkWriteTarget.java @@ -0,0 +1,57 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.engine.data.chunk.TerrainChunk; +import art.arcane.iris.spi.ChunkWriteTarget; +import art.arcane.iris.spi.PlatformBiome; +import art.arcane.iris.spi.PlatformBlockState; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; + +/** + * Bukkit adapter for the hot-path chunk write surface backed by TerrainChunk. + */ +public final class BukkitChunkWriteTarget implements ChunkWriteTarget { + private final TerrainChunk terrain; + + public BukkitChunkWriteTarget(TerrainChunk terrain) { + this.terrain = terrain; + } + + @Override + public void setBlock(int x, int y, int z, PlatformBlockState state) { + terrain.setBlock(x, y, z, (BlockData) state.nativeHandle()); + } + + @Override + public void setBiome(int x, int y, int z, PlatformBiome biome) { + terrain.setBiome(x, y, z, (Biome) biome.nativeHandle()); + } + + @Override + public int minHeight() { + return terrain.getMinHeight(); + } + + @Override + public int maxHeight() { + return terrain.getMaxHeight(); + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java new file mode 100644 index 000000000..4d394c3d6 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitEntityType.java @@ -0,0 +1,62 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.PlatformEntityType; +import org.bukkit.entity.EntityType; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Interned Bukkit adapter for a neutral entity type handle. + */ +public final class BukkitEntityType implements PlatformEntityType { + private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + + private final EntityType type; + private final String key; + private final String namespace; + + private BukkitEntityType(EntityType type, String key) { + this.type = type; + this.key = key; + int colon = key.indexOf(':'); + this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft"; + } + + public static BukkitEntityType of(EntityType type) { + String key = type.getKey().toString(); + return CACHE.computeIfAbsent(key, (String k) -> new BukkitEntityType(type, k)); + } + + @Override + public String key() { + return key; + } + + @Override + public String namespace() { + return namespace; + } + + @Override + public Object nativeHandle() { + return type; + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java new file mode 100644 index 000000000..f98b0ed1b --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitItem.java @@ -0,0 +1,62 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.PlatformItem; +import org.bukkit.Material; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * Interned Bukkit adapter for a neutral item handle backed by Material. + */ +public final class BukkitItem implements PlatformItem { + private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>(); + + private final Material material; + private final String key; + private final String namespace; + + private BukkitItem(Material material, String key) { + this.material = material; + this.key = key; + int colon = key.indexOf(':'); + this.namespace = colon >= 0 ? key.substring(0, colon) : "minecraft"; + } + + public static BukkitItem of(Material material) { + String key = material.getKey().toString(); + return CACHE.computeIfAbsent(key, (String k) -> new BukkitItem(material, k)); + } + + @Override + public String key() { + return key; + } + + @Override + public String namespace() { + return namespace; + } + + @Override + public Object nativeHandle() { + return material; + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java new file mode 100644 index 000000000..7338208c3 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitPlatform.java @@ -0,0 +1,102 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.nms.INMS; +import art.arcane.iris.spi.IrisPlatform; +import art.arcane.iris.spi.LogLevel; +import art.arcane.iris.spi.PlatformCapabilities; +import art.arcane.iris.spi.PlatformRegistries; +import art.arcane.iris.spi.PlatformScheduler; +import art.arcane.iris.util.common.scheduling.J; +import org.bukkit.Bukkit; + +import java.io.File; + +/** + * Bukkit implementation of the root Iris platform service. + */ +public final class BukkitPlatform implements IrisPlatform { + private final BukkitRegistries registries = new BukkitRegistries(); + private final BukkitScheduler scheduler = new BukkitScheduler(); + private final PlatformCapabilities capabilities = new BukkitCapabilities(); + + @Override + public String platformName() { + return "bukkit"; + } + + @Override + public String minecraftVersion() { + return Bukkit.getBukkitVersion(); + } + + @Override + public PlatformRegistries registries() { + return registries; + } + + @Override + public PlatformScheduler scheduler() { + return scheduler; + } + + @Override + public PlatformCapabilities capabilities() { + return capabilities; + } + + @Override + public File dataFolder() { + return Iris.instance.getDataFolder(); + } + + @Override + public void log(LogLevel level, String message) { + switch (level) { + case DEBUG -> Iris.debug(message); + case INFO -> Iris.info(message); + case WARN -> Iris.warn(message); + case ERROR -> Iris.error(message); + } + } + + private static final class BukkitCapabilities implements PlatformCapabilities { + @Override + public boolean customBiomes() { + return INMS.get().supportsCustomBiomes(); + } + + @Override + public boolean dataPacks() { + return INMS.get().supportsDataPacks(); + } + + @Override + public boolean structurePlacement() { + return true; + } + + @Override + public boolean regionizedThreading() { + return J.isFolia(); + } + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java new file mode 100644 index 000000000..5b4bf20aa --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitRegistries.java @@ -0,0 +1,98 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.core.nms.INMS; +import art.arcane.iris.spi.PlatformBiome; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.spi.PlatformEntityType; +import art.arcane.iris.spi.PlatformItem; +import art.arcane.iris.spi.PlatformRegistries; +import art.arcane.iris.util.common.data.B; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.EntityType; + +import java.util.ArrayList; +import java.util.List; + +/** + * Bukkit adapter resolving namespaced keys against the live Bukkit registries. + */ +public final class BukkitRegistries implements PlatformRegistries { + @Override + public PlatformBlockState block(String key) { + BlockData data = B.get(key); + return data == null ? null : BukkitBlockState.of(data); + } + + @Override + public PlatformBiome biome(String key) { + NamespacedKey namespacedKey = NamespacedKey.fromString(key); + if (namespacedKey == null) { + return null; + } + Biome biome = Registry.BIOME.get(namespacedKey); + return biome == null ? null : BukkitBiome.of(biome); + } + + @Override + public PlatformItem item(String key) { + Material material = Material.matchMaterial(key); + return material == null ? null : BukkitItem.of(material); + } + + @Override + public PlatformEntityType entity(String key) { + NamespacedKey namespacedKey = NamespacedKey.fromString(key); + if (namespacedKey == null) { + return null; + } + EntityType type = Registry.ENTITY_TYPE.get(namespacedKey); + return type == null ? null : BukkitEntityType.of(type); + } + + @Override + public List blockKeys() { + List keys = new ArrayList<>(); + for (Material material : Registry.MATERIAL) { + if (material.isBlock()) { + keys.add(material.getKey().toString()); + } + } + return keys; + } + + @Override + public List biomeKeys() { + List keys = new ArrayList<>(); + for (Biome biome : Registry.BIOME) { + keys.add(BukkitBiome.of(biome).key()); + } + return keys; + } + + @Override + public List structureKeys() { + return new ArrayList<>(INMS.get().getStructureKeys()); + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java new file mode 100644 index 000000000..70766410f --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitScheduler.java @@ -0,0 +1,60 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.PlatformScheduler; +import art.arcane.iris.spi.PlatformWorld; +import art.arcane.iris.util.common.scheduling.J; +import org.bukkit.World; + +/** + * Bukkit adapter dispatching platform tasks through the Folia-aware J scheduler. + */ +public final class BukkitScheduler implements PlatformScheduler { + @Override + public void global(Runnable task) { + J.s(task); + } + + @Override + public void region(PlatformWorld world, int chunkX, int chunkZ, Runnable task) { + World bukkitWorld = (World) world.nativeHandle(); + if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task)) { + J.s(task); + } + } + + @Override + public void async(Runnable task) { + J.a(task); + } + + @Override + public void laterGlobal(Runnable task, int ticks) { + J.s(task, ticks); + } + + @Override + public void laterRegion(PlatformWorld world, int chunkX, int chunkZ, Runnable task, int ticks) { + World bukkitWorld = (World) world.nativeHandle(); + if (!J.runRegion(bukkitWorld, chunkX, chunkZ, task, ticks)) { + J.s(task, ticks); + } + } +} diff --git a/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java new file mode 100644 index 000000000..ed91c7ba3 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/platform/bukkit/BukkitWorld.java @@ -0,0 +1,81 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.PlatformBiome; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.spi.PlatformWorld; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; + +/** + * Bukkit adapter for a neutral world view backed by org.bukkit.World. + */ +public final class BukkitWorld implements PlatformWorld { + private final World world; + + public BukkitWorld(World world) { + this.world = world; + } + + @Override + public String name() { + return world.getName(); + } + + @Override + public long seed() { + return world.getSeed(); + } + + @Override + public int minHeight() { + return world.getMinHeight(); + } + + @Override + public int maxHeight() { + return world.getMaxHeight(); + } + + @Override + public PlatformBlockState getBlock(int x, int y, int z) { + return BukkitBlockState.of(world.getBlockAt(x, y, z).getBlockData()); + } + + @Override + public void setBlock(int x, int y, int z, PlatformBlockState block, int flags) { + world.getBlockAt(x, y, z).setBlockData((BlockData) block.nativeHandle(), (flags & 1) != 0); + } + + @Override + public PlatformBiome getBiome(int x, int y, int z) { + return BukkitBiome.of(world.getBiome(x, y, z)); + } + + @Override + public boolean isChunkLoaded(int chunkX, int chunkZ) { + return world.isChunkLoaded(chunkX, chunkZ); + } + + @Override + public Object nativeHandle() { + return world; + } +} diff --git a/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java new file mode 100644 index 000000000..3c20b63e6 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/platform/bukkit/BukkitSpiConformanceTest.java @@ -0,0 +1,131 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.platform.bukkit; + +import art.arcane.iris.spi.IrisPlatform; +import art.arcane.iris.spi.IrisPlatforms; +import art.arcane.iris.spi.PlatformBlockState; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.block.data.BlockData; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; + +import java.util.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class BukkitSpiConformanceTest { + @BeforeClass + public static void setup() { + Server server = Bukkit.getServer(); + if (server == null) { + server = mock(Server.class); + doReturn(Logger.getLogger("IrisTest")).when(server).getLogger(); + doReturn("IrisTestServer").when(server).getName(); + doReturn("1.0").when(server).getVersion(); + doReturn("1.0").when(server).getBukkitVersion(); + doReturn(mock(BlockData.class)).when(server).createBlockData(any(Material.class)); + Bukkit.setServer(server); + } + doAnswer((InvocationOnMock invocation) -> blockData(invocation.getArgument(0))).when(server).createBlockData(anyString()); + } + + private static BlockData blockData(String asString) { + BlockData data = mock(BlockData.class); + doReturn(asString).when(data).getAsString(); + return data; + } + + @Test + public void blockStateInterningReturnsSameInstanceForSameKey() { + BlockData first = blockData("iristest:intern_block[axis=y]"); + BlockData second = blockData("iristest:intern_block[axis=y]"); + assertSame(BukkitBlockState.of(first), BukkitBlockState.of(second)); + } + + @Test + public void blockStateKeyMatchesCanonicalString() { + BlockData data = blockData("iristest:key_block[facing=north,lit=true]"); + assertEquals("iristest:key_block[facing=north,lit=true]", BukkitBlockState.of(data).key()); + } + + @Test + public void blockStateNamespaceParsesWithAndWithoutProperties() { + BlockData withProps = blockData("iristest:ns_block[axis=y]"); + BlockData withoutProps = blockData("iristest:ns_plain"); + assertEquals("iristest", BukkitBlockState.of(withProps).namespace()); + assertEquals("iristest", BukkitBlockState.of(withoutProps).namespace()); + } + + @Test + public void withPropertyReplacesExistingProperty() { + BlockData data = blockData("iristest:merge_block[axis=y,waterlogged=false]"); + PlatformBlockState merged = BukkitBlockState.of(data).withProperty("axis", "x"); + assertEquals("iristest:merge_block[axis=x,waterlogged=false]", merged.key()); + } + + @Test + public void withPropertyAppendsToExistingProperties() { + BlockData data = blockData("iristest:append_block[axis=y]"); + PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true"); + assertEquals("iristest:append_block[axis=y,lit=true]", merged.key()); + } + + @Test + public void withPropertyAddsBracketSectionWhenAbsent() { + BlockData data = blockData("iristest:bare_block"); + PlatformBlockState merged = BukkitBlockState.of(data).withProperty("lit", "true"); + assertEquals("iristest:bare_block[lit=true]", merged.key()); + } + + @Test + public void platformBindingLifecycle() { + assertFalse(IrisPlatforms.isBound()); + try { + IrisPlatforms.get(); + fail("get() must throw while unbound"); + } catch (IllegalStateException expected) { + } + IrisPlatform first = mock(IrisPlatform.class); + IrisPlatforms.bind(first); + assertTrue(IrisPlatforms.isBound()); + assertSame(first, IrisPlatforms.get()); + IrisPlatforms.bind(first); + assertSame(first, IrisPlatforms.get()); + IrisPlatform second = mock(IrisPlatform.class); + try { + IrisPlatforms.bind(second); + fail("bind() must reject a different instance"); + } catch (IllegalStateException expected) { + } + assertSame(first, IrisPlatforms.get()); + } +} diff --git a/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java new file mode 100644 index 000000000..b36ef62e5 --- /dev/null +++ b/spi/src/main/java/art/arcane/iris/spi/IrisPlatforms.java @@ -0,0 +1,48 @@ +/* + * Iris is a World Generator for Minecraft Bukkit 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 . + */ + +package art.arcane.iris.spi; + +/** + * Static holder binding the active platform adapter for the lifetime of the runtime. + */ +public final class IrisPlatforms { + private static volatile IrisPlatform platform; + + private IrisPlatforms() { + } + + public static synchronized void bind(IrisPlatform p) { + if (platform != null && platform != p) { + throw new IllegalStateException("Iris platform is already bound to a different instance"); + } + platform = p; + } + + public static IrisPlatform get() { + IrisPlatform bound = platform; + if (bound == null) { + throw new IllegalStateException("No Iris platform is bound"); + } + return bound; + } + + public static boolean isBound() { + return platform != null; + } +}