mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-12 18:04:01 +00:00
laDEDA
This commit is contained in:
@@ -30,7 +30,10 @@ Properties rootProperties = new Properties()
|
||||
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
|
||||
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.1'))
|
||||
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.1.2'))
|
||||
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.3')
|
||||
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse(rootProperties.getProperty('fabricLoaderVersion', '0.19.3'))
|
||||
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||
}
|
||||
|
||||
group = 'art.arcane'
|
||||
version = irisVersion
|
||||
@@ -177,7 +180,10 @@ processResources {
|
||||
}
|
||||
|
||||
tasks.named('shadowJar', ShadowJar).configure {
|
||||
archiveFileName.set("Iris-${project.version}+mc${minecraftVersion}-fabric.jar")
|
||||
doFirst {
|
||||
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
|
||||
}
|
||||
archiveFileName.set(irisArtifactName('Fabric', "${minecraftVersion}+${fabricLoaderVersion}"))
|
||||
configurations = [project.configurations.named('bundle').get()]
|
||||
mergeServiceFiles()
|
||||
exclude('META-INF/maven/**')
|
||||
|
||||
@@ -58,7 +58,6 @@ public final class IrisFabricBootstrap implements ModInitializer {
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
ModdedEngineBootstrap.initialize(new FabricModdedLoader());
|
||||
art.arcane.iris.modded.ModdedPackInstaller.ensureDefaultPack(ModdedEngineBootstrap.loader().configDir());
|
||||
FabricLoader loader = FabricLoader.getInstance();
|
||||
String modVersion = loader.getModContainer("irisworldgen")
|
||||
.map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"id": "irisworldgen",
|
||||
"version": "${version}",
|
||||
"name": "Iris",
|
||||
"description": "Iris World Generation Engine (Fabric adapter - native chunk generator, overworld override datapack, engine lifecycle)",
|
||||
"description": "Iris World Generation Engine (Fabric adapter - native chunk generator, explicit Iris world datapack workflow, engine lifecycle)",
|
||||
"authors": ["Arcane Arts (Volmit Software)"],
|
||||
"contact": {
|
||||
"sources": "https://github.com/VolmitSoftware/Iris"
|
||||
|
||||
@@ -30,7 +30,18 @@ Properties rootProperties = new Properties()
|
||||
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
|
||||
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.1'))
|
||||
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.1.2'))
|
||||
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.1.2-64.0.9')
|
||||
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse(rootProperties.getProperty('forgeVersion', '26.1.2-64.0.9'))
|
||||
Closure<String> loaderDisplayVersion = { String loaderVersion ->
|
||||
String coordinatePrefix = "${minecraftVersion}-"
|
||||
if (loaderVersion.startsWith(coordinatePrefix)) {
|
||||
return loaderVersion.substring(coordinatePrefix.length())
|
||||
}
|
||||
|
||||
return loaderVersion
|
||||
}
|
||||
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||
}
|
||||
|
||||
group = 'art.arcane'
|
||||
version = irisVersion
|
||||
@@ -198,7 +209,10 @@ processResources {
|
||||
}
|
||||
|
||||
tasks.named('shadowJar', ShadowJar).configure {
|
||||
archiveFileName.set("Iris-${project.version}+mc${minecraftVersion}-forge.jar")
|
||||
doFirst {
|
||||
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
|
||||
}
|
||||
archiveFileName.set(irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}"))
|
||||
configurations = [project.configurations.named('bundle').get()]
|
||||
mergeServiceFiles()
|
||||
exclude('META-INF/maven/**')
|
||||
|
||||
@@ -51,7 +51,6 @@ public final class IrisForgeBootstrap {
|
||||
|
||||
public IrisForgeBootstrap(FMLJavaModLoadingContext context) {
|
||||
ModdedEngineBootstrap.initialize(new ForgeModdedLoader());
|
||||
art.arcane.iris.modded.ModdedPackInstaller.ensureDefaultPack(ModdedEngineBootstrap.loader().configDir());
|
||||
String modVersion = ModList.getModContainerById("irisworldgen")
|
||||
.map((ModContainer container) -> container.getModInfo().getVersion().toString())
|
||||
.orElse("unknown");
|
||||
|
||||
@@ -6,7 +6,7 @@ license = "GPL-3.0"
|
||||
modId = "irisworldgen"
|
||||
version = "${version}"
|
||||
displayName = "Iris"
|
||||
description = "Iris World Generation Engine (Forge adapter - native chunk generator, overworld override datapack, engine lifecycle)"
|
||||
description = "Iris World Generation Engine (Forge adapter - native chunk generator, explicit Iris world datapack workflow, engine lifecycle)"
|
||||
authors = "Arcane Arts (Volmit Software)"
|
||||
|
||||
[[dependencies.irisworldgen]]
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||
public static final MapCodec<IrisModdedChunkGenerator> CODEC = RecordCodecBuilder.mapCodec((RecordCodecBuilder.Instance<IrisModdedChunkGenerator> instance) -> instance.group(
|
||||
BiomeSource.CODEC.fieldOf("biome_source").forGetter((IrisModdedChunkGenerator generator) -> generator.biomeSource),
|
||||
Codec.STRING.optionalFieldOf("dimension", "overworld").forGetter((IrisModdedChunkGenerator generator) -> generator.dimensionKey)
|
||||
Codec.STRING.fieldOf("dimension").forGetter((IrisModdedChunkGenerator generator) -> generator.dimensionKey)
|
||||
).apply(instance, IrisModdedChunkGenerator::new));
|
||||
|
||||
private final String dimensionKey;
|
||||
|
||||
@@ -20,17 +20,55 @@ package art.arcane.iris.modded;
|
||||
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
import art.arcane.iris.spi.PlatformBiomeWriter;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class ModdedBiomeWriter implements PlatformBiomeWriter {
|
||||
private final Supplier<MinecraftServer> server;
|
||||
|
||||
public ModdedBiomeWriter(Supplier<MinecraftServer> server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int biomeIdFor(String key) {
|
||||
return 0;
|
||||
Registry<Biome> registry = biomeRegistry();
|
||||
Identifier identifier = Identifier.tryParse(key);
|
||||
if (registry == null || identifier == null) {
|
||||
return 0;
|
||||
}
|
||||
Biome biome = registry.getValue(identifier);
|
||||
return biome == null ? 0 : registry.getId(biome);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PlatformBiome> allBiomes() {
|
||||
return List.of();
|
||||
Registry<Biome> registry = biomeRegistry();
|
||||
List<PlatformBiome> biomes = new ArrayList<>();
|
||||
if (registry == null) {
|
||||
return biomes;
|
||||
}
|
||||
for (Identifier identifier : registry.keySet()) {
|
||||
Biome biome = registry.getValue(identifier);
|
||||
if (biome != null) {
|
||||
biomes.add(ModdedBiome.of(biome, identifier.toString()));
|
||||
}
|
||||
}
|
||||
return biomes;
|
||||
}
|
||||
|
||||
private Registry<Biome> biomeRegistry() {
|
||||
MinecraftServer instance = server.get();
|
||||
if (instance == null) {
|
||||
return null;
|
||||
}
|
||||
return instance.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,29 +40,12 @@ import java.util.stream.Stream;
|
||||
|
||||
public final class ModdedPackInstaller {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||
private static final String DEFAULT_PACK = "overworld";
|
||||
private static final String DEFAULT_BRANCH = "master";
|
||||
private static final Pattern PACK_NAME = Pattern.compile("[a-z0-9_-]+");
|
||||
private static final Pattern BRANCH_NAME = Pattern.compile("[A-Za-z0-9._-]+");
|
||||
|
||||
private ModdedPackInstaller() {
|
||||
}
|
||||
|
||||
public static void ensureDefaultPack(Path configDir) {
|
||||
Path target = configDir.resolve("irisworldgen").resolve("packs").resolve(DEFAULT_PACK);
|
||||
|
||||
if (Files.isDirectory(target.resolve("dimensions"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Iris default pack missing; downloading IrisDimensions/{} (latest on {})", DEFAULT_PACK, DEFAULT_BRANCH);
|
||||
boolean installed = install(configDir, DEFAULT_PACK, DEFAULT_BRANCH, LOGGER::info);
|
||||
|
||||
if (!installed) {
|
||||
LOGGER.error("Iris failed to download the default '{}' pack; install it manually at {} (source: https://github.com/IrisDimensions/overworld)", DEFAULT_PACK, target);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean install(Path configDir, String pack, String branch, Consumer<String> feedback) {
|
||||
if (pack == null || !PACK_NAME.matcher(pack).matches()) {
|
||||
feedback.accept("Invalid pack name '" + pack + "' (allowed: a-z, 0-9, _ and -)");
|
||||
|
||||
@@ -49,7 +49,7 @@ public final class ModdedPlatform implements IrisPlatform {
|
||||
this.scheduler = new ModdedScheduler();
|
||||
this.capabilities = new ModdedCapabilities();
|
||||
this.structureHooks = new ModdedStructureHooks(loader::currentServer);
|
||||
this.biomeWriter = new ModdedBiomeWriter();
|
||||
this.biomeWriter = new ModdedBiomeWriter(loader::currentServer);
|
||||
}
|
||||
|
||||
public static void errorSink(Consumer<Throwable> sink) {
|
||||
|
||||
@@ -86,16 +86,22 @@ public final class ModdedWorldCheck {
|
||||
}
|
||||
|
||||
private static boolean run(MinecraftServer server) {
|
||||
ServerLevel overworld = server.overworld();
|
||||
String generatorClass = overworld.getChunkSource().getGenerator().getClass().getName();
|
||||
LOGGER.info("[worldcheck] overworld generator: {}", generatorClass);
|
||||
boolean irisGenerator = overworld.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator;
|
||||
if (!irisGenerator) {
|
||||
LOGGER.error("[worldcheck] overworld is NOT using IrisModdedChunkGenerator");
|
||||
ServerLevel level = targetLevel(server);
|
||||
if (level == null) {
|
||||
LOGGER.error("[worldcheck] no Iris dimension is loaded");
|
||||
return false;
|
||||
}
|
||||
|
||||
BlockPos spawn = overworld.getRespawnData().pos();
|
||||
LOGGER.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), overworld.getMinY(), overworld.getHeight());
|
||||
String levelId = level.dimension().identifier().toString();
|
||||
String generatorClass = level.getChunkSource().getGenerator().getClass().getName();
|
||||
LOGGER.info("[worldcheck] {} generator: {}", levelId, generatorClass);
|
||||
boolean irisGenerator = level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator;
|
||||
if (!irisGenerator) {
|
||||
LOGGER.error("[worldcheck] {} is NOT using IrisModdedChunkGenerator", levelId);
|
||||
}
|
||||
|
||||
BlockPos spawn = level.getRespawnData().pos();
|
||||
LOGGER.info("[worldcheck] spawn: {} {} {} (minY={} height={})", spawn.getX(), spawn.getY(), spawn.getZ(), level.getMinY(), level.getHeight());
|
||||
|
||||
MessageDigest digest = sha256();
|
||||
List<String> samples = new ArrayList<>();
|
||||
@@ -104,9 +110,9 @@ public final class ModdedWorldCheck {
|
||||
for (int dz = 0; dz < 4; dz++) {
|
||||
int x = spawn.getX() + (dx - 2) * 16 + 8;
|
||||
int z = spawn.getZ() + (dz - 2) * 16 + 8;
|
||||
overworld.getChunk(x >> 4, z >> 4);
|
||||
int y = overworld.getHeight(Heightmap.Types.WORLD_SURFACE, x, z);
|
||||
BlockState surface = overworld.getBlockState(new BlockPos(x, y - 1, z));
|
||||
level.getChunk(x >> 4, z >> 4);
|
||||
int y = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z);
|
||||
BlockState surface = level.getBlockState(new BlockPos(x, y - 1, z));
|
||||
String key = BuiltInRegistries.BLOCK.getKey(surface.getBlock()).toString();
|
||||
String line = x + " " + (y - 1) + " " + z + " " + key;
|
||||
samples.add(line);
|
||||
@@ -121,7 +127,7 @@ public final class ModdedWorldCheck {
|
||||
LOGGER.info("[worldcheck] surface digest: {} ({} columns, {} distinct surface blocks: {})",
|
||||
HexFormat.of().formatHex(digest.digest()).substring(0, 12), samples.size(), surfaceKeys.size(), surfaceKeys);
|
||||
|
||||
ChunkAccess zeroChunk = overworld.getChunk(0, 0);
|
||||
ChunkAccess zeroChunk = level.getChunk(0, 0);
|
||||
int nonEmptySections = 0;
|
||||
for (LevelChunkSection section : zeroChunk.getSections()) {
|
||||
if (!section.hasOnlyAir()) {
|
||||
@@ -129,8 +135,8 @@ public final class ModdedWorldCheck {
|
||||
}
|
||||
}
|
||||
Set<String> columnKeys = new LinkedHashSet<>();
|
||||
int surfaceY = overworld.getHeight(Heightmap.Types.WORLD_SURFACE, 8, 8);
|
||||
for (int y = overworld.getMinY(); y < surfaceY; y += 16) {
|
||||
int surfaceY = level.getHeight(Heightmap.Types.WORLD_SURFACE, 8, 8);
|
||||
for (int y = level.getMinY(); y < surfaceY; y += 16) {
|
||||
BlockState state = zeroChunk.getBlockState(new BlockPos(8, y, 8));
|
||||
if (!state.isAir()) {
|
||||
columnKeys.add(BuiltInRegistries.BLOCK.getKey(state.getBlock()).toString());
|
||||
@@ -153,6 +159,26 @@ public final class ModdedWorldCheck {
|
||||
return pass;
|
||||
}
|
||||
|
||||
private static ServerLevel targetLevel(MinecraftServer server) {
|
||||
String target = System.getProperty("iris.worldcheck.dimension");
|
||||
if (target != null && !target.isBlank()) {
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
if (level.dimension().identifier().toString().equals(target.trim())) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
LOGGER.error("[worldcheck] requested dimension '{}' is not loaded", target);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
if (level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MessageDigest sha256() {
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
|
||||
+84
-29
@@ -38,12 +38,12 @@ import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
@@ -74,15 +74,25 @@ public final class IrisModdedCommands {
|
||||
private static final SuggestionProvider<CommandSourceStack> OBJECT_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestObjectKeys(context, builder);
|
||||
private static final SuggestionProvider<CommandSourceStack> STRUCTURE_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestStructureKeys(context, builder);
|
||||
private static final SuggestionProvider<CommandSourceStack> POI_TYPES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> SharedSuggestionProvider.suggest(List.of("buried_treasure"), builder);
|
||||
static final SuggestionProvider<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(builder);
|
||||
static final SuggestionProvider<CommandSourceStack> PACK_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestPackNames(context, builder);
|
||||
private static final SuggestionProvider<CommandSourceStack> DIMENSION_NAMES = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestDimensionNames(context, builder);
|
||||
|
||||
private IrisModdedCommands() {
|
||||
}
|
||||
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> root = dispatcher.register(rootTree());
|
||||
dispatcher.register(Commands.literal("ir").redirect(root));
|
||||
dispatcher.register(Commands.literal("irs").redirect(root));
|
||||
LOGGER.info("Iris /iris command tree registered");
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> rootTree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("iris");
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), ""));
|
||||
root.then(helpTree());
|
||||
|
||||
root.then(Commands.literal("version")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> version(context.getSource())));
|
||||
|
||||
@@ -100,40 +110,74 @@ public final class IrisModdedCommands {
|
||||
root.then(Commands.literal("seed").requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> seed(context.getSource())));
|
||||
|
||||
root.then(goldenhashTree());
|
||||
root.then(goldenhashTree("goldenhash"));
|
||||
root.then(goldenhashTree("gold"));
|
||||
|
||||
root.then(Commands.literal("download").requires(GATE)
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), "master"))
|
||||
.then(Commands.argument("branch", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), StringArgumentType.getString(context, "branch"))))));
|
||||
root.then(downloadTree("download"));
|
||||
root.then(downloadTree("dl"));
|
||||
|
||||
root.then(Commands.literal("metrics").requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> metrics(context.getSource())));
|
||||
root.then(metricsTree("metrics"));
|
||||
root.then(metricsTree("measure"));
|
||||
|
||||
root.then(Commands.literal("regen").requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> regen(context.getSource(), 0))
|
||||
.then(Commands.argument("radius", IntegerArgumentType.integer(0, 64))
|
||||
.executes((CommandContext<CommandSourceStack> context) -> regen(context.getSource(), IntegerArgumentType.getInteger(context, "radius")))));
|
||||
root.then(regenTree("regen"));
|
||||
root.then(regenTree("rg"));
|
||||
|
||||
root.then(pregenTree());
|
||||
root.then(pregenTree("pregen"));
|
||||
root.then(pregenTree("pregenerate"));
|
||||
|
||||
root.then(Commands.literal("wand").requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedObjectCommands.giveWand(context.getSource())));
|
||||
root.then(ModdedObjectCommands.tree());
|
||||
root.then(ModdedObjectCommands.tree("object"));
|
||||
root.then(ModdedObjectCommands.tree("o"));
|
||||
root.then(editTree());
|
||||
|
||||
root.then(ModdedStudioCommands.tree());
|
||||
root.then(ModdedPackCommands.tree());
|
||||
root.then(ModdedDatapackCommands.tree());
|
||||
root.then(ModdedStructureCommands.tree());
|
||||
root.then(ModdedStudioCommands.tree("studio"));
|
||||
root.then(ModdedStudioCommands.tree("std"));
|
||||
root.then(ModdedStudioCommands.tree("s"));
|
||||
root.then(ModdedPackCommands.tree("pack"));
|
||||
root.then(ModdedPackCommands.tree("pk"));
|
||||
root.then(ModdedWorldCommands.tree("world"));
|
||||
root.then(ModdedWorldCommands.tree("w"));
|
||||
root.then(ModdedDatapackCommands.tree("datapack"));
|
||||
root.then(ModdedDatapackCommands.tree("datapacks"));
|
||||
root.then(ModdedDatapackCommands.tree("dp"));
|
||||
root.then(ModdedStructureCommands.tree("structure"));
|
||||
root.then(ModdedStructureCommands.tree("struct"));
|
||||
root.then(ModdedStructureCommands.tree("str"));
|
||||
|
||||
dispatcher.register(root);
|
||||
LOGGER.info("Iris /iris command tree registered");
|
||||
return root;
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> helpTree() {
|
||||
return Commands.literal("help")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), ""))
|
||||
.then(Commands.argument("section", StringArgumentType.greedyString())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), StringArgumentType.getString(context, "section"))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> downloadTree(String name) {
|
||||
return Commands.literal(name).requires(GATE)
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), "stable"))
|
||||
.then(Commands.argument("branch", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> download(context.getSource(), StringArgumentType.getString(context, "pack"), StringArgumentType.getString(context, "branch")))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> metricsTree(String name) {
|
||||
return Commands.literal(name).requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> metrics(context.getSource()));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> regenTree(String name) {
|
||||
return Commands.literal(name).requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> regen(context.getSource(), 0))
|
||||
.then(Commands.argument("radius", IntegerArgumentType.integer(0, 64))
|
||||
.executes((CommandContext<CommandSourceStack> context) -> regen(context.getSource(), IntegerArgumentType.getInteger(context, "radius"))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> gotoTree(String name) {
|
||||
return Commands.literal(name).requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name))
|
||||
.then(Commands.literal("biome")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(BIOME_KEYS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> gotoBiome(context.getSource(), StringArgumentType.getString(context, "key")))))
|
||||
@@ -151,8 +195,9 @@ public final class IrisModdedCommands {
|
||||
.executes((CommandContext<CommandSourceStack> context) -> gotoPoi(context.getSource(), StringArgumentType.getString(context, "type")))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> pregenTree() {
|
||||
return Commands.literal("pregen").requires(GATE)
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> pregenTree(String name) {
|
||||
return Commands.literal(name).requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name))
|
||||
.then(Commands.literal("start")
|
||||
.then(Commands.argument("radius", IntegerArgumentType.integer(1, 100000))
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenStart(context.getSource(), IntegerArgumentType.getInteger(context, "radius"), 0, 0))
|
||||
@@ -164,14 +209,18 @@ public final class IrisModdedCommands {
|
||||
IntegerArgumentType.getInteger(context, "z")))))))
|
||||
.then(Commands.literal("stop")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenStop(context.getSource())))
|
||||
.then(Commands.literal("x")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenStop(context.getSource())))
|
||||
.then(Commands.literal("pause")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenPause(context.getSource())))
|
||||
.then(Commands.literal("resume")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenPause(context.getSource())))
|
||||
.then(Commands.literal("status")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pregenStatus(context.getSource())));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> goldenhashTree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> radiusAndThreads = Commands.literal("goldenhash").requires(GATE)
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> goldenhashTree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> radiusAndThreads = Commands.literal(name).requires(GATE)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> goldenhash(context.getSource(), 8, 8, ModdedGoldenHash.Mode.AUTO));
|
||||
attachModes(radiusAndThreads, (CommandContext<CommandSourceStack> context) -> 8, (CommandContext<CommandSourceStack> context) -> 8);
|
||||
|
||||
@@ -617,6 +666,7 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestBiomeKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
@@ -628,6 +678,7 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestRegionKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
@@ -639,6 +690,7 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestObjectKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
@@ -650,6 +702,7 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestStructureKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
@@ -660,7 +713,8 @@ public final class IrisModdedCommands {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestPackNames(SuggestionsBuilder builder) {
|
||||
private static CompletableFuture<Suggestions> suggestPackNames(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("overworld");
|
||||
try {
|
||||
@@ -679,6 +733,7 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestDimensionNames(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
List<String> names = new ArrayList<>();
|
||||
for (ServerLevel level : context.getSource().getServer().getAllLevels()) {
|
||||
if (level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator) {
|
||||
@@ -689,10 +744,10 @@ public final class IrisModdedCommands {
|
||||
}
|
||||
|
||||
static void ok(CommandSourceStack source, String message) {
|
||||
source.sendSuccess(() -> Component.literal(message), false);
|
||||
ModdedCommandFeedback.ok(source, message);
|
||||
}
|
||||
|
||||
static void fail(CommandSourceStack source, String message) {
|
||||
source.sendFailure(Component.literal(message));
|
||||
ModdedCommandFeedback.fail(source, message);
|
||||
}
|
||||
}
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
final class ModdedCommandFeedback {
|
||||
private static final long MESSAGE_SOUND_COOLDOWN_MS = 650L;
|
||||
private static final long TAB_SOUND_COOLDOWN_MS = 175L;
|
||||
private static final Map<UUID, Long> MESSAGE_SOUNDS = new ConcurrentHashMap<>();
|
||||
private static final Map<UUID, Long> TAB_SOUNDS = new ConcurrentHashMap<>();
|
||||
|
||||
private ModdedCommandFeedback() {
|
||||
}
|
||||
|
||||
static void ok(CommandSourceStack source, String message) {
|
||||
source.sendSuccess(() -> Component.literal(message).withStyle(ChatFormatting.GREEN), false);
|
||||
playSuccess(source);
|
||||
}
|
||||
|
||||
static void fail(CommandSourceStack source, String message) {
|
||||
source.sendFailure(Component.literal(message).withStyle(ChatFormatting.RED));
|
||||
playFailure(source);
|
||||
}
|
||||
|
||||
static void send(CommandSourceStack source, Component component) {
|
||||
source.sendSuccess(() -> component, false);
|
||||
}
|
||||
|
||||
static void tab(CommandSourceStack source) {
|
||||
ServerPlayer player = source.getPlayer();
|
||||
if (player == null || !claim(TAB_SOUNDS, player.getUUID(), TAB_SOUND_COOLDOWN_MS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.level().playSound(null, player.blockPosition(), SoundEvents.ITEM_FRAME_ROTATE_ITEM, SoundSource.PLAYERS, 0.25F, 1.7F);
|
||||
}
|
||||
|
||||
private static void playSuccess(CommandSourceStack source) {
|
||||
ServerPlayer player = source.getPlayer();
|
||||
if (player == null || !claim(MESSAGE_SOUNDS, player.getUUID(), MESSAGE_SOUND_COOLDOWN_MS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = player.level();
|
||||
level.playSound(null, player.blockPosition(), SoundEvents.AMETHYST_CLUSTER_BREAK, SoundSource.PLAYERS, 0.77F, 1.65F);
|
||||
level.playSound(null, player.blockPosition(), SoundEvents.RESPAWN_ANCHOR_CHARGE, SoundSource.PLAYERS, 0.125F, 2.99F);
|
||||
}
|
||||
|
||||
private static void playFailure(CommandSourceStack source) {
|
||||
ServerPlayer player = source.getPlayer();
|
||||
if (player == null || !claim(MESSAGE_SOUNDS, player.getUUID(), MESSAGE_SOUND_COOLDOWN_MS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel level = player.level();
|
||||
level.playSound(null, player.blockPosition(), SoundEvents.AMETHYST_CLUSTER_BREAK, SoundSource.PLAYERS, 0.77F, 0.25F);
|
||||
level.playSound(null, player.blockPosition(), SoundEvents.BEACON_DEACTIVATE, SoundSource.PLAYERS, 0.2F, 0.45F);
|
||||
}
|
||||
|
||||
private static boolean claim(Map<UUID, Long> sounds, UUID uuid, long cooldownMs) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long previous = sounds.get(uuid);
|
||||
if (previous != null && now - previous.longValue() < cooldownMs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sounds.put(uuid, now);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.HoverEvent;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class ModdedCommandHelp {
|
||||
private static final Map<String, List<Entry>> SECTIONS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
SECTIONS.put("", List.of(
|
||||
Entry.command("version", "", "Print version information"),
|
||||
Entry.command("info", "[dimension]", "List loaded Iris dimensions and pack details"),
|
||||
Entry.command("what", "", "Inspect the Iris biome, region, cave biome, surface and chunk at your position"),
|
||||
Entry.group("find", "Find and teleport to Iris biomes, regions, objects, structures and points of interest", "goto"),
|
||||
Entry.command("seed", "", "Print world and engine seed information"),
|
||||
Entry.command("download", "<pack> [branch]", "Download a pack project", "dl"),
|
||||
Entry.command("metrics", "", "Print generation metrics for your current Iris dimension", "measure"),
|
||||
Entry.command("regen", "[radius]", "Delete and regenerate nearby chunks in place", "rg"),
|
||||
Entry.group("pregen", "Pregenerate an Iris dimension", "pregenerate"),
|
||||
Entry.command("wand", "", "Get an Iris object wand"),
|
||||
Entry.group("object", "Object wand, save, paste, analyze and undo tools", "o"),
|
||||
Entry.group("studio", "Pack project creation, packaging and reports", "std", "s"),
|
||||
Entry.group("pack", "Pack validation and maintenance", "pk"),
|
||||
Entry.group("world", "Explicit Iris dimension enablement and removal", "w"),
|
||||
Entry.group("datapack", "World datapack install and status helpers", "datapacks", "dp"),
|
||||
Entry.group("structure", "Iris structure index, info and placement tools", "struct", "str"),
|
||||
Entry.command("goldenhash", "[radius] [threads] [capture|verify]", "Generate deterministic block hashes for parity testing", "gold")
|
||||
));
|
||||
SECTIONS.put("find", List.of(
|
||||
Entry.command("biome", "<key>", "Find an Iris biome"),
|
||||
Entry.command("region", "<key>", "Find an Iris region"),
|
||||
Entry.command("object", "<key>", "Find an object placement"),
|
||||
Entry.command("structure", "<key>", "Find an Iris-placed structure"),
|
||||
Entry.command("poi", "<type>", "Find a supported point of interest")
|
||||
));
|
||||
SECTIONS.put("goto", SECTIONS.get("find"));
|
||||
SECTIONS.put("pregen", List.of(
|
||||
Entry.command("start", "<radius> [x] [z]", "Start pregeneration"),
|
||||
Entry.command("stop", "", "Stop the active pregeneration task", "x"),
|
||||
Entry.command("pause", "", "Pause or resume pregeneration", "resume"),
|
||||
Entry.command("status", "", "Show pregeneration status")
|
||||
));
|
||||
SECTIONS.put("pregenerate", SECTIONS.get("pregen"));
|
||||
SECTIONS.put("object", List.of(
|
||||
Entry.command("wand", "", "Get an Iris object wand"),
|
||||
Entry.command("dust", "", "Get dust that reveals object placements", "d"),
|
||||
Entry.command("save", "<name>", "Save the selected wand volume as an object"),
|
||||
Entry.command("paste", "<key>", "Paste an object at your position"),
|
||||
Entry.command("expand", "[amount]", "Expand the wand selection in your looking direction"),
|
||||
Entry.command("contract", "[amount]", "Contract the wand selection in your looking direction", "-"),
|
||||
Entry.command("shift", "[amount]", "Shift the wand selection in your looking direction"),
|
||||
Entry.command("position1", "[look]", "Set selection point 1", "p1"),
|
||||
Entry.command("position2", "[look]", "Set selection point 2", "p2"),
|
||||
Entry.command("x+y", "", "Autoselect up and out"),
|
||||
Entry.command("x&y", "", "Autoselect up, down and out"),
|
||||
Entry.command("analyze", "<key>", "Show object composition"),
|
||||
Entry.command("shrink", "<key>", "Shrink an object to its minimum size"),
|
||||
Entry.command("undo", "[amount]", "Undo pasted objects", "u")
|
||||
));
|
||||
SECTIONS.put("o", SECTIONS.get("object"));
|
||||
SECTIONS.put("studio", List.of(
|
||||
Entry.command("create", "<name> [template]", "Create a new pack project", "+"),
|
||||
Entry.command("package", "[pack]", "Package a dimension into a compressed format"),
|
||||
Entry.command("version", "[pack]", "Print a pack version"),
|
||||
Entry.command("regions", "[radius]", "Calculate nearby region distribution"),
|
||||
Entry.command("open", "", "Explain modded studio workflow"),
|
||||
Entry.command("close", "", "Explain modded studio workflow"),
|
||||
Entry.command("vscode", "", "Explain editor workflow"),
|
||||
Entry.command("update", "", "Explain workspace regeneration workflow"),
|
||||
Entry.command("importvanilla", "", "Explain vanilla import workflow", "importv", "iv")
|
||||
));
|
||||
SECTIONS.put("std", SECTIONS.get("studio"));
|
||||
SECTIONS.put("s", SECTIONS.get("studio"));
|
||||
SECTIONS.put("pack", List.of(
|
||||
Entry.command("validate", "[pack]", "Validate a pack or every pack", "v"),
|
||||
Entry.command("restore", "<pack>", "Restore the latest trashed files for a pack", "r"),
|
||||
Entry.command("status", "[pack]", "Show cached validation status", "s")
|
||||
));
|
||||
SECTIONS.put("pk", SECTIONS.get("pack"));
|
||||
SECTIONS.put("world", List.of(
|
||||
Entry.command("enable", "<dimension> <pack> [packDimension]", "Create an Iris dimension in world/datapacks/iris", "create"),
|
||||
Entry.command("replace-overworld", "<pack> [packDimension]", "Explicitly make minecraft:overworld use an Iris pack"),
|
||||
Entry.command("disable", "<dimension>", "Remove an Iris dimension definition from the world datapack", "remove", "rm"),
|
||||
Entry.command("list", "", "List Iris dimensions staged in the world datapack", "ls"),
|
||||
Entry.command("status", "", "Show staged and currently loaded Iris dimensions")
|
||||
));
|
||||
SECTIONS.put("w", SECTIONS.get("world"));
|
||||
SECTIONS.put("datapack", List.of(
|
||||
Entry.command("status", "", "Check loaded Iris dimension type overrides"),
|
||||
Entry.command("install", "", "Install dimension type overrides for loaded Iris dimensions"),
|
||||
Entry.command("list", "", "List configured and installed datapacks", "ls"),
|
||||
Entry.command("ingest", "", "Explain Bukkit datapack ingest workflow", "pull"),
|
||||
Entry.command("remove", "<id>", "Explain datapack removal workflow", "rm")
|
||||
));
|
||||
SECTIONS.put("datapacks", SECTIONS.get("datapack"));
|
||||
SECTIONS.put("dp", SECTIONS.get("datapack"));
|
||||
SECTIONS.put("structure", List.of(
|
||||
Entry.command("list", "", "Regenerate structure-index.json", "ls"),
|
||||
Entry.command("info", "<key>", "Resolve an Iris structure graph and report bounds"),
|
||||
Entry.command("place", "<key>", "Assemble and place an Iris structure at your location", "p"),
|
||||
Entry.command("import", "", "Explain Bukkit structure import workflow", "import-all", "reimport", "imp", "all"),
|
||||
Entry.command("capture", "", "Explain Bukkit structure capture workflow", "cap"),
|
||||
Entry.command("verify", "", "Explain modded structure locate behavior", "locateall")
|
||||
));
|
||||
SECTIONS.put("struct", SECTIONS.get("structure"));
|
||||
SECTIONS.put("str", SECTIONS.get("structure"));
|
||||
}
|
||||
|
||||
private ModdedCommandHelp() {
|
||||
}
|
||||
|
||||
static int send(CommandSourceStack source, String path) {
|
||||
String normalized = normalize(path);
|
||||
List<Entry> entries = SECTIONS.get(normalized);
|
||||
if (entries == null) {
|
||||
ModdedCommandFeedback.fail(source, "Unknown Iris help section: " + normalized);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sendHeader(source, normalized.isEmpty() ? "Iris Commands" : "/iris " + normalized);
|
||||
if (!normalized.isEmpty()) {
|
||||
ModdedCommandFeedback.send(source, clickable(" < Back", "/iris", "/iris", "Back to Iris command groups", true));
|
||||
}
|
||||
for (Entry entry : entries) {
|
||||
ModdedCommandFeedback.send(source, line(normalized, entry));
|
||||
}
|
||||
ModdedCommandFeedback.ok(source, "Click a group to open it, or click a command to place it in chat.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static void sendHeader(CommandSourceStack source, String title) {
|
||||
MutableComponent header = Component.literal("========== ").withStyle(ChatFormatting.DARK_GREEN, ChatFormatting.STRIKETHROUGH)
|
||||
.append(Component.literal(" " + title + " ").withStyle(ChatFormatting.GREEN, ChatFormatting.BOLD))
|
||||
.append(Component.literal(" ==========").withStyle(ChatFormatting.DARK_GREEN, ChatFormatting.STRIKETHROUGH));
|
||||
ModdedCommandFeedback.send(source, header);
|
||||
}
|
||||
|
||||
private static MutableComponent line(String path, Entry entry) {
|
||||
String basePath = path.isEmpty() ? "" : " " + path;
|
||||
String command = "/iris" + basePath + " " + entry.name();
|
||||
String suggestion = entry.usage().isBlank() ? command : command + " " + entry.usage();
|
||||
String hover = entry.description() + (entry.usage().isBlank() ? "" : "\nUsage: " + suggestion);
|
||||
MutableComponent row = Component.literal(" ");
|
||||
row.append(clickable(entry.name(), command, suggestion, hover, entry.group()));
|
||||
if (entry.aliases().length > 0) {
|
||||
row.append(Component.literal(" " + String.join(", ", entry.aliases())).withStyle(ChatFormatting.DARK_GREEN));
|
||||
}
|
||||
row.append(Component.literal(" - ").withStyle(ChatFormatting.DARK_GRAY));
|
||||
row.append(Component.literal(entry.description()).withStyle(ChatFormatting.GRAY));
|
||||
return row;
|
||||
}
|
||||
|
||||
private static MutableComponent clickable(String label, String command, String suggestion, String hover, boolean run) {
|
||||
Component hoverText = Component.literal(hover).withStyle(ChatFormatting.GREEN);
|
||||
if (run) {
|
||||
return Component.literal(label).withStyle((style) -> style
|
||||
.withColor(ChatFormatting.AQUA)
|
||||
.withBold(true)
|
||||
.withClickEvent(new ClickEvent.RunCommand(command))
|
||||
.withHoverEvent(new HoverEvent.ShowText(hoverText)));
|
||||
}
|
||||
|
||||
return Component.literal(label).withStyle((style) -> style
|
||||
.withColor(ChatFormatting.GREEN)
|
||||
.withClickEvent(new ClickEvent.SuggestCommand(suggestion))
|
||||
.withHoverEvent(new HoverEvent.ShowText(hoverText)));
|
||||
}
|
||||
|
||||
private static String normalize(String path) {
|
||||
if (path == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String normalized = path.trim().toLowerCase();
|
||||
if (normalized.startsWith("/iris ")) {
|
||||
normalized = normalized.substring(6).trim();
|
||||
} else if (normalized.equals("/iris")) {
|
||||
normalized = "";
|
||||
}
|
||||
|
||||
if (normalized.startsWith("help ")) {
|
||||
normalized = normalized.substring(5).trim();
|
||||
}
|
||||
|
||||
int space = normalized.indexOf(' ');
|
||||
if (space >= 0) {
|
||||
normalized = normalized.substring(0, space);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private record Entry(String name, String usage, String description, boolean group, String... aliases) {
|
||||
static Entry command(String name, String usage, String description, String... aliases) {
|
||||
return new Entry(name, usage, description, false, aliases);
|
||||
}
|
||||
|
||||
static Entry group(String name, String description, String... aliases) {
|
||||
return new Entry(name, "", description, true, aliases);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-5
@@ -48,13 +48,15 @@ import java.util.function.Predicate;
|
||||
public final class ModdedDatapackCommands {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
private static final String WORLD_PACK_NAME = "iris";
|
||||
private static final String WORLD_PACK_NAME = ModdedWorldDatapackWriter.WORLD_PACK_NAME;
|
||||
|
||||
private ModdedDatapackCommands() {
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("datapack").requires(GATE);
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("status")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource())));
|
||||
@@ -64,10 +66,14 @@ public final class ModdedDatapackCommands {
|
||||
|
||||
root.then(Commands.literal("list")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
root.then(Commands.literal("ls")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
|
||||
root.then(message("ingest", "Modrinth datapack ingest requires the Bukkit plugin: its post-restart structure import into editable Iris resources uses Bukkit registries, and Iris modded dimensions do not run vanilla structure placement, so ingested structure datapacks would not generate. Drop datapacks into world/datapacks manually for non-structure content."));
|
||||
root.then(message("pull", "Modrinth datapack ingest requires the Bukkit plugin: its post-restart structure import into editable Iris resources uses Bukkit registries, and Iris modded dimensions do not run vanilla structure placement, so ingested structure datapacks would not generate. Drop datapacks into world/datapacks manually for non-structure content."));
|
||||
|
||||
root.then(message("remove", "Datapack removal manages the Bukkit ingest manifest. On modded servers delete the datapack folder from world/datapacks and restart."));
|
||||
root.then(message("rm", "Datapack removal manages the Bukkit ingest manifest. On modded servers delete the datapack folder from world/datapacks and restart."));
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -130,7 +136,7 @@ public final class ModdedDatapackCommands {
|
||||
if (!matches) {
|
||||
mismatches++;
|
||||
IrisModdedCommands.fail(source, " WARNING: the active dimension type does not match the pack. Terrain outside "
|
||||
+ activeMin + ".." + activeMax + " will be clipped. Run /iris datapack install and restart the server.");
|
||||
+ activeMin + ".." + activeMax + " will be clipped. Run /iris world enable <dimension> <pack> for new worlds, or /iris datapack install for already-loaded Iris dimensions, then restart.");
|
||||
}
|
||||
}
|
||||
if (irisLevels == 0) {
|
||||
@@ -202,7 +208,7 @@ public final class ModdedDatapackCommands {
|
||||
for (String path : written) {
|
||||
IrisModdedCommands.ok(source, "Wrote " + path);
|
||||
}
|
||||
IrisModdedCommands.ok(source, "World datapack '" + WORLD_PACK_NAME + "' installed. Restart the server for the dimension types to apply (world datapacks override mod-provided data).");
|
||||
IrisModdedCommands.ok(source, "World datapack '" + WORLD_PACK_NAME + "' dimension type overrides installed. Restart the server for the dimension types to apply.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -27,7 +27,6 @@ import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.slf4j.Logger;
|
||||
@@ -91,7 +90,7 @@ public final class ModdedGoldenHash {
|
||||
|
||||
public static void start(CommandSourceStack source, ServerLevel level, Engine engine, int radius, int threads, Mode mode) {
|
||||
if (!ACTIVE.compareAndSet(false, true)) {
|
||||
source.sendFailure(Component.literal("A goldenhash scan is already running."));
|
||||
IrisModdedCommands.fail(source, "A goldenhash scan is already running.");
|
||||
return;
|
||||
}
|
||||
ModdedGoldenHash scan = new ModdedGoldenHash(source, level, engine, radius, threads, mode);
|
||||
@@ -375,11 +374,11 @@ public final class ModdedGoldenHash {
|
||||
}
|
||||
|
||||
private void ok(String message) {
|
||||
server.execute(() -> source.sendSuccess(() -> Component.literal(message), false));
|
||||
server.execute(() -> IrisModdedCommands.ok(source, message));
|
||||
}
|
||||
|
||||
private void fail(String message) {
|
||||
server.execute(() -> source.sendFailure(Component.literal(message)));
|
||||
server.execute(() -> IrisModdedCommands.fail(source, message));
|
||||
}
|
||||
|
||||
private static String shortHash(String hex) {
|
||||
|
||||
+14
-2
@@ -66,6 +66,7 @@ public final class ModdedObjectCommands {
|
||||
private static final double TARGET_RANGE = 256.0D;
|
||||
|
||||
private static final SuggestionProvider<CommandSourceStack> OBJECT_KEYS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = IrisModdedCommands.engineFor(context.getSource().getLevel());
|
||||
if (engine != null) {
|
||||
@@ -88,14 +89,18 @@ public final class ModdedObjectCommands {
|
||||
SHIFT
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
ModdedObjectUndo.init();
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("object").requires(GATE);
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("wand")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> giveWand(context.getSource())));
|
||||
root.then(Commands.literal("dust")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> giveDust(context.getSource())));
|
||||
root.then(Commands.literal("d")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> giveDust(context.getSource())));
|
||||
|
||||
root.then(Commands.literal("save")
|
||||
.then(Commands.literal("overwrite")
|
||||
@@ -108,6 +113,7 @@ public final class ModdedObjectCommands {
|
||||
|
||||
root.then(resizeTree("expand", ResizeOp.EXPAND));
|
||||
root.then(resizeTree("contract", ResizeOp.CONTRACT));
|
||||
root.then(resizeTree("-", ResizeOp.CONTRACT));
|
||||
root.then(resizeTree("shift", ResizeOp.SHIFT));
|
||||
|
||||
root.then(Commands.literal("xpy")
|
||||
@@ -120,7 +126,9 @@ public final class ModdedObjectCommands {
|
||||
.executes((CommandContext<CommandSourceStack> context) -> autoSelect(context.getSource(), true)));
|
||||
|
||||
root.then(positionTree("position1", true));
|
||||
root.then(positionTree("p1", true));
|
||||
root.then(positionTree("position2", false));
|
||||
root.then(positionTree("p2", false));
|
||||
|
||||
root.then(Commands.literal("analyze")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(OBJECT_KEYS)
|
||||
@@ -134,6 +142,10 @@ public final class ModdedObjectCommands {
|
||||
.executes((CommandContext<CommandSourceStack> context) -> undo(context.getSource(), 1))
|
||||
.then(Commands.argument("amount", IntegerArgumentType.integer(1, 32))
|
||||
.executes((CommandContext<CommandSourceStack> context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount")))));
|
||||
root.then(Commands.literal("u")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> undo(context.getSource(), 1))
|
||||
.then(Commands.argument("amount", IntegerArgumentType.integer(1, 32))
|
||||
.executes((CommandContext<CommandSourceStack> context) -> undo(context.getSource(), IntegerArgumentType.getInteger(context, "amount")))));
|
||||
|
||||
root.then(bukkitOnly("we", "WorldEdit selection import requires the Bukkit plugin with WorldEdit installed."));
|
||||
root.then(bukkitOnly("studio", "The object studio world requires the Bukkit studio toolchain; it is not available on modded servers."));
|
||||
|
||||
+15
-2
@@ -44,22 +44,35 @@ public final class ModdedPackCommands {
|
||||
private ModdedPackCommands() {
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("pack").requires(GATE);
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("validate")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> validate(context.getSource(), null))
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
root.then(Commands.literal("v")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> validate(context.getSource(), null))
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> validate(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
|
||||
root.then(Commands.literal("restore")
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
root.then(Commands.literal("r")
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> restore(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
|
||||
root.then(Commands.literal("status")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource(), null))
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
root.then(Commands.literal("s")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource(), null))
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource(), StringArgumentType.getString(context, "pack")))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
+3
-4
@@ -34,7 +34,6 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
@@ -89,7 +88,7 @@ public final class ModdedRegen {
|
||||
|
||||
public static void start(CommandSourceStack source, ServerLevel level, IrisModdedChunkGenerator generator, Engine engine, ServerPlayer player, int radius) {
|
||||
if (!ACTIVE.compareAndSet(false, true)) {
|
||||
source.sendFailure(Component.literal("A regen is already running."));
|
||||
IrisModdedCommands.fail(source, "A regen is already running.");
|
||||
return;
|
||||
}
|
||||
int centerX = player.blockPosition().getX() >> 4;
|
||||
@@ -276,10 +275,10 @@ public final class ModdedRegen {
|
||||
}
|
||||
|
||||
private void ok(String message) {
|
||||
server.execute(() -> source.sendSuccess(() -> Component.literal(message), false));
|
||||
server.execute(() -> IrisModdedCommands.ok(source, message));
|
||||
}
|
||||
|
||||
private void fail(String message) {
|
||||
server.execute(() -> source.sendFailure(Component.literal(message)));
|
||||
server.execute(() -> IrisModdedCommands.fail(source, message));
|
||||
}
|
||||
}
|
||||
|
||||
+16
-2
@@ -56,11 +56,15 @@ public final class ModdedStructureCommands {
|
||||
private ModdedStructureCommands() {
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("structure").requires(GATE);
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("list")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
root.then(Commands.literal("ls")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
|
||||
root.then(Commands.literal("info")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS)
|
||||
@@ -69,10 +73,19 @@ public final class ModdedStructureCommands {
|
||||
root.then(Commands.literal("place")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> place(context.getSource(), StringArgumentType.getString(context, "key")))));
|
||||
root.then(Commands.literal("p")
|
||||
.then(Commands.argument("key", StringArgumentType.greedyString()).suggests(IRIS_STRUCTURE_KEYS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> place(context.getSource(), StringArgumentType.getString(context, "key")))));
|
||||
|
||||
root.then(message("import", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("import-all", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("reimport", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("imp", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("all", "Structure import rebuilds vanilla & datapack structures as editable Iris resources through Bukkit/NMS template managers; run /iris structure import on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("capture", "Structure capture generates each structure in a throwaway Bukkit scratch world to read its blocks; it requires the Bukkit plugin (v26 NMS binding)."));
|
||||
root.then(message("cap", "Structure capture generates each structure in a throwaway Bukkit scratch world to read its blocks; it requires the Bukkit plugin (v26 NMS binding)."));
|
||||
root.then(message("verify", "Vanilla structure locate is meaningless here: Iris modded dimensions do not run vanilla structure placement (Iris places structures itself). Use /iris goto structure <key> to locate Iris-placed structures."));
|
||||
root.then(message("locateall", "Vanilla structure locate is meaningless here: Iris modded dimensions do not run vanilla structure placement (Iris places structures itself). Use /iris goto structure <key> to locate Iris-placed structures."));
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -194,6 +207,7 @@ public final class ModdedStructureCommands {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestIrisStructureKeys(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
ModdedCommandFeedback.tab(context.getSource());
|
||||
try {
|
||||
Engine engine = IrisModdedCommands.engineFor(context.getSource().getLevel());
|
||||
if (engine != null && engine.getData().getStructureLoader() != null) {
|
||||
|
||||
+19
-2
@@ -71,14 +71,21 @@ public final class ModdedStudioCommands {
|
||||
private ModdedStudioCommands() {
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree() {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("studio").requires(GATE);
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("create")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))
|
||||
.then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template"))))));
|
||||
root.then(Commands.literal("+")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), DEFAULT_TEMPLATE))
|
||||
.then(Commands.argument("template", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> create(context.getSource(), StringArgumentType.getString(context, "name"), StringArgumentType.getString(context, "template"))))));
|
||||
|
||||
root.then(Commands.literal("package")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> pkg(context.getSource(), null))
|
||||
@@ -96,17 +103,27 @@ public final class ModdedStudioCommands {
|
||||
.executes((CommandContext<CommandSourceStack> context) -> regions(context.getSource(), IntegerArgumentType.getInteger(context, "radius")))));
|
||||
|
||||
root.then(message("open", "Studio worlds are temporary Bukkit worlds opened by the Bukkit studio toolchain; modded servers cannot open ad-hoc runtime dimensions. Edit the pack under config/irisworldgen/packs/<pack> and create a fresh world (or /iris regen) to see changes."));
|
||||
root.then(message("o", "Studio worlds are temporary Bukkit worlds opened by the Bukkit studio toolchain; modded servers cannot open ad-hoc runtime dimensions. Edit the pack under config/irisworldgen/packs/<pack> and create a fresh world (or /iris regen) to see changes."));
|
||||
root.then(message("close", "There are no studio worlds on modded servers (/iris studio open is Bukkit-only), so there is nothing to close."));
|
||||
root.then(message("x", "There are no studio worlds on modded servers (/iris studio open is Bukkit-only), so there is nothing to close."));
|
||||
root.then(message("tpstudio", "There are no studio worlds on modded servers to teleport to; /iris studio open is Bukkit-only."));
|
||||
root.then(message("stp", "There are no studio worlds on modded servers to teleport to; /iris studio open is Bukkit-only."));
|
||||
root.then(message("vscode", "VSCode launch and workspace generation are desktop features of the Bukkit studio toolchain; edit config/irisworldgen/packs/<pack> directly in your editor."));
|
||||
root.then(message("vsc", "VSCode launch and workspace generation are desktop features of the Bukkit studio toolchain; edit config/irisworldgen/packs/<pack> directly in your editor."));
|
||||
root.then(message("update", "Workspace regeneration (.code-workspace + JSON schemas) reads Bukkit registries (SchemaBuilder); run /iris studio update on a Bukkit server against this pack."));
|
||||
root.then(message("importvanilla", "Vanilla tree/object/structure capture generates features in throwaway Bukkit worlds via NMS; run /iris studio importvanilla on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("importv", "Vanilla tree/object/structure capture generates features in throwaway Bukkit worlds via NMS; run /iris studio importvanilla on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("iv", "Vanilla tree/object/structure capture generates features in throwaway Bukkit worlds via NMS; run /iris studio importvanilla on a Bukkit server against this pack, then copy the pack folder over."));
|
||||
root.then(message("noise", "The noise explorer is a desktop GUI launched from the Bukkit plugin."));
|
||||
root.then(message("nmap", "The noise explorer is a desktop GUI launched from the Bukkit plugin."));
|
||||
root.then(message("map", "The world map renderer is a desktop GUI launched from the Bukkit plugin."));
|
||||
root.then(message("render", "The world map renderer is a desktop GUI launched from the Bukkit plugin."));
|
||||
root.then(message("loot", "Loot simulation opens a Bukkit chest inventory GUI; it is not available on modded servers."));
|
||||
root.then(message("profile", "Pack performance profiling is part of the Bukkit studio toolchain and is not ported to modded servers."));
|
||||
root.then(message("spawn", "Iris entity spawning uses the Bukkit entity pipeline and is not ported to modded servers."));
|
||||
root.then(message("summon", "Iris entity spawning uses the Bukkit entity pipeline and is not ported to modded servers."));
|
||||
root.then(message("objects", "The chunk object report reads Bukkit chunk data and is not ported to modded servers."));
|
||||
root.then(message("find-objects", "The chunk object report reads Bukkit chunk data and is not ported to modded servers."));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.SharedSuggestionProvider;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class ModdedWorldCommands {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
|
||||
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
|
||||
private static final SuggestionProvider<CommandSourceStack> ENABLED_DIMENSIONS = (CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) -> suggestEnabledDimensions(context, builder);
|
||||
|
||||
private ModdedWorldCommands() {
|
||||
}
|
||||
|
||||
public static LiteralArgumentBuilder<CommandSourceStack> tree(String name) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal(name).requires(GATE);
|
||||
|
||||
root.executes((CommandContext<CommandSourceStack> context) -> ModdedCommandHelp.send(context.getSource(), name));
|
||||
|
||||
root.then(Commands.literal("status")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> status(context.getSource())));
|
||||
root.then(Commands.literal("list")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
root.then(Commands.literal("ls")
|
||||
.executes((CommandContext<CommandSourceStack> context) -> list(context.getSource())));
|
||||
|
||||
root.then(enableTree("enable"));
|
||||
root.then(enableTree("create"));
|
||||
root.then(replaceOverworldTree());
|
||||
|
||||
root.then(Commands.literal("disable")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word()).suggests(ENABLED_DIMENSIONS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> disable(context.getSource(), StringArgumentType.getString(context, "dimension")))));
|
||||
root.then(Commands.literal("remove")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word()).suggests(ENABLED_DIMENSIONS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> disable(context.getSource(), StringArgumentType.getString(context, "dimension")))));
|
||||
root.then(Commands.literal("rm")
|
||||
.then(Commands.argument("dimension", StringArgumentType.word()).suggests(ENABLED_DIMENSIONS)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> disable(context.getSource(), StringArgumentType.getString(context, "dimension")))));
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> enableTree(String name) {
|
||||
return Commands.literal(name)
|
||||
.then(Commands.argument("dimension", StringArgumentType.word())
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> enable(context.getSource(),
|
||||
StringArgumentType.getString(context, "dimension"),
|
||||
StringArgumentType.getString(context, "pack"),
|
||||
StringArgumentType.getString(context, "pack")))
|
||||
.then(Commands.argument("packDimension", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> enable(context.getSource(),
|
||||
StringArgumentType.getString(context, "dimension"),
|
||||
StringArgumentType.getString(context, "pack"),
|
||||
StringArgumentType.getString(context, "packDimension"))))));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> replaceOverworldTree() {
|
||||
return Commands.literal("replace-overworld")
|
||||
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> enable(context.getSource(),
|
||||
"minecraft:overworld",
|
||||
StringArgumentType.getString(context, "pack"),
|
||||
StringArgumentType.getString(context, "pack")))
|
||||
.then(Commands.argument("packDimension", StringArgumentType.word())
|
||||
.executes((CommandContext<CommandSourceStack> context) -> enable(context.getSource(),
|
||||
"minecraft:overworld",
|
||||
StringArgumentType.getString(context, "pack"),
|
||||
StringArgumentType.getString(context, "packDimension")))));
|
||||
}
|
||||
|
||||
private static int enable(CommandSourceStack source, String targetDimension, String packName, String packDimension) {
|
||||
try {
|
||||
ModdedWorldDatapackWriter.WriteResult result = ModdedWorldDatapackWriter.enable(source.getServer(), targetDimension, packName, packDimension);
|
||||
IrisModdedCommands.ok(source, "Enabled Iris world " + result.targetDimension() + " using pack '" + result.packName() + "' dimension '" + result.packDimension() + "'.");
|
||||
IrisModdedCommands.ok(source, "Wrote " + result.dimensionFile().getPath());
|
||||
IrisModdedCommands.ok(source, "Wrote " + result.typeFile().getPath());
|
||||
IrisModdedCommands.ok(source, "Wrote " + result.packMetaFile().getPath());
|
||||
IrisModdedCommands.ok(source, "Restart the server, then use /execute in " + result.targetDimension() + " run tp <player> 0 100 0 or a portal/modded dimension tool.");
|
||||
if ("minecraft:overworld".equals(result.targetDimension())) {
|
||||
IrisModdedCommands.ok(source, "minecraft:overworld replacement was explicitly requested.");
|
||||
}
|
||||
return 1;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Iris world datapack write failed for {}", targetDimension, e);
|
||||
IrisModdedCommands.fail(source, "Failed to write Iris world datapack: " + e.getMessage());
|
||||
return 0;
|
||||
} catch (IllegalArgumentException e) {
|
||||
IrisModdedCommands.fail(source, e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int disable(CommandSourceStack source, String targetDimension) {
|
||||
try {
|
||||
File removed = ModdedWorldDatapackWriter.disable(source.getServer(), targetDimension);
|
||||
IrisModdedCommands.ok(source, "Removed " + removed.getPath());
|
||||
IrisModdedCommands.ok(source, "Restart the server for the dimension removal to apply.");
|
||||
return 1;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Iris world datapack removal failed for {}", targetDimension, e);
|
||||
IrisModdedCommands.fail(source, "Failed to remove Iris world datapack entry: " + e.getMessage());
|
||||
return 0;
|
||||
} catch (IllegalArgumentException e) {
|
||||
IrisModdedCommands.fail(source, e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int status(CommandSourceStack source) {
|
||||
list(source);
|
||||
int loaded = 0;
|
||||
MinecraftServer server = source.getServer();
|
||||
for (ServerLevel level : server.getAllLevels()) {
|
||||
if (level.getChunkSource().getGenerator() instanceof IrisModdedChunkGenerator generator) {
|
||||
loaded++;
|
||||
IrisModdedCommands.ok(source, "Loaded Iris level: " + level.dimension().identifier() + " -> pack dimension '" + generator.dimensionKey() + "'");
|
||||
}
|
||||
}
|
||||
if (loaded == 0) {
|
||||
IrisModdedCommands.fail(source, "No Iris dimensions are currently loaded. Enabled dimensions require a server restart before Minecraft loads them.");
|
||||
}
|
||||
return loaded > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
private static int list(CommandSourceStack source) {
|
||||
try {
|
||||
List<String> dimensions = ModdedWorldDatapackWriter.enabledDimensions(source.getServer());
|
||||
IrisModdedCommands.ok(source, "Enabled Iris world datapack dimensions: " + dimensions.size());
|
||||
for (String dimension : dimensions) {
|
||||
IrisModdedCommands.ok(source, " - " + dimension);
|
||||
}
|
||||
if (dimensions.isEmpty()) {
|
||||
IrisModdedCommands.ok(source, "Use /iris world enable <dimension> <pack> to create one without replacing the main world.");
|
||||
}
|
||||
return 1;
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Iris world datapack listing failed", e);
|
||||
IrisModdedCommands.fail(source, "Failed to list Iris world datapack dimensions: " + e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> suggestEnabledDimensions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
|
||||
try {
|
||||
return SharedSuggestionProvider.suggest(ModdedWorldDatapackWriter.enabledDimensions(context.getSource().getServer()), builder);
|
||||
} catch (IOException e) {
|
||||
return Suggestions.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
+217
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Iris is a World Generator for Minecraft Servers
|
||||
* Copyright (c) 2026 Arcane Arts (Volmit Software)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package art.arcane.iris.modded.command;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.datapack.DataVersion;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
|
||||
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.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final class ModdedWorldDatapackWriter {
|
||||
static final String WORLD_PACK_NAME = "iris";
|
||||
private static final String DEFAULT_NAMESPACE = "irisworldgen";
|
||||
private static final String TYPE_NAMESPACE = "irisworldgen";
|
||||
|
||||
private ModdedWorldDatapackWriter() {
|
||||
}
|
||||
|
||||
static WriteResult enable(MinecraftServer server, String targetDimension, String packName, String packDimension) throws IOException {
|
||||
ResourceTarget target = ResourceTarget.parse(targetDimension);
|
||||
String cleanPackName = cleanPackSegment(packName, "pack");
|
||||
String cleanPackDimension = cleanPackSegment(packDimension, "pack dimension");
|
||||
File packFolder = new File(ModdedPackCommands.packsRoot(), cleanPackName);
|
||||
if (!packFolder.isDirectory()) {
|
||||
throw new IllegalArgumentException("Pack '" + cleanPackName + "' was not found under " + ModdedPackCommands.packsRoot().getAbsolutePath());
|
||||
}
|
||||
|
||||
IrisData data = IrisData.get(packFolder);
|
||||
IrisDimension dimension = data.getDimensionLoader().load(cleanPackDimension);
|
||||
if (dimension == null) {
|
||||
throw new IllegalArgumentException("Pack '" + cleanPackName + "' does not contain dimensions/" + cleanPackDimension + ".json");
|
||||
}
|
||||
|
||||
String typeKey = IrisDimension.sanitizeDimensionTypeKeyValue(cleanPackDimension);
|
||||
File typeFile = dimensionTypeFile(server, typeKey);
|
||||
File dimensionFile = dimensionFile(server, target);
|
||||
File mcmeta = packMetaFile(server);
|
||||
|
||||
writeParented(typeFile, dimension.getDimensionType().toJson(DataVersion.getLatest().get()));
|
||||
writeParented(dimensionFile, dimensionJson(cleanPackDimension, typeKey));
|
||||
writePackMeta(mcmeta);
|
||||
|
||||
return new WriteResult(target.id(), cleanPackName, cleanPackDimension, dimensionFile, typeFile, mcmeta);
|
||||
}
|
||||
|
||||
static File disable(MinecraftServer server, String targetDimension) throws IOException {
|
||||
ResourceTarget target = ResourceTarget.parse(targetDimension);
|
||||
File dimensionFile = dimensionFile(server, target);
|
||||
if (!dimensionFile.isFile()) {
|
||||
throw new IllegalArgumentException("No Iris world datapack dimension exists for " + target.id());
|
||||
}
|
||||
Files.delete(dimensionFile.toPath());
|
||||
return dimensionFile;
|
||||
}
|
||||
|
||||
static List<String> enabledDimensions(MinecraftServer server) throws IOException {
|
||||
File dataRoot = new File(irisPackFolder(server), "data");
|
||||
if (!dataRoot.isDirectory()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<String> dimensions = new ArrayList<>();
|
||||
File[] namespaces = dataRoot.listFiles(File::isDirectory);
|
||||
if (namespaces == null) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
for (File namespace : namespaces) {
|
||||
File dimensionRoot = new File(namespace, "dimension");
|
||||
if (!dimensionRoot.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
collectDimensionFiles(namespace.getName(), dimensionRoot, dimensions);
|
||||
}
|
||||
Collections.sort(dimensions);
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
static File packMetaFile(MinecraftServer server) {
|
||||
return new File(irisPackFolder(server), "pack.mcmeta");
|
||||
}
|
||||
|
||||
private static void collectDimensionFiles(String namespace, File dimensionRoot, List<String> dimensions) throws IOException {
|
||||
Path root = dimensionRoot.toPath();
|
||||
try (Stream<Path> stream = Files.walk(root)) {
|
||||
stream.filter(Files::isRegularFile)
|
||||
.filter((Path path) -> path.getFileName().toString().endsWith(".json"))
|
||||
.forEach((Path path) -> {
|
||||
String relative = root.relativize(path).toString().replace(File.separatorChar, '/');
|
||||
String idPath = relative.substring(0, relative.length() - ".json".length());
|
||||
dimensions.add(namespace + ":" + idPath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static File worldDatapacksFolder(MinecraftServer server) {
|
||||
return server.getWorldPath(LevelResource.DATAPACK_DIR).toFile();
|
||||
}
|
||||
|
||||
private static File irisPackFolder(MinecraftServer server) {
|
||||
return new File(worldDatapacksFolder(server), WORLD_PACK_NAME);
|
||||
}
|
||||
|
||||
private static File dimensionTypeFile(MinecraftServer server, String typeKey) {
|
||||
return new File(irisPackFolder(server), "data/" + TYPE_NAMESPACE + "/dimension_type/" + typeKey + ".json");
|
||||
}
|
||||
|
||||
private static File dimensionFile(MinecraftServer server, ResourceTarget target) {
|
||||
return new File(new File(irisPackFolder(server), "data/" + target.namespace() + "/dimension"), target.path() + ".json");
|
||||
}
|
||||
|
||||
private static void writePackMeta(File output) throws IOException {
|
||||
int packFormat = DataVersion.getLatest().getPackFormat();
|
||||
String json = "{\n"
|
||||
+ " \"pack\": {\n"
|
||||
+ " \"description\": \"Iris world and dimension type definitions.\",\n"
|
||||
+ " \"pack_format\": " + packFormat + ",\n"
|
||||
+ " \"min_format\": " + packFormat + ",\n"
|
||||
+ " \"max_format\": " + packFormat + "\n"
|
||||
+ " }\n"
|
||||
+ "}\n";
|
||||
writeParented(output, json);
|
||||
}
|
||||
|
||||
private static void writeParented(File output, String text) throws IOException {
|
||||
File parent = output.getParentFile();
|
||||
if (parent != null) {
|
||||
parent.mkdirs();
|
||||
}
|
||||
Files.writeString(output.toPath(), text, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String dimensionJson(String packDimension, String typeKey) {
|
||||
return "{\n"
|
||||
+ " \"type\": \"" + TYPE_NAMESPACE + ":" + typeKey + "\",\n"
|
||||
+ " \"generator\": {\n"
|
||||
+ " \"type\": \"irisworldgen:iris\",\n"
|
||||
+ " \"dimension\": \"" + escape(packDimension) + "\",\n"
|
||||
+ " \"biome_source\": {\n"
|
||||
+ " \"type\": \"minecraft:fixed\",\n"
|
||||
+ " \"biome\": \"minecraft:plains\"\n"
|
||||
+ " }\n"
|
||||
+ " }\n"
|
||||
+ "}\n";
|
||||
}
|
||||
|
||||
private static String cleanPackSegment(String value, String label) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("Missing " + label + ".");
|
||||
}
|
||||
String clean = value.trim();
|
||||
if (clean.contains("/") || clean.contains("..") || clean.contains("\\") || clean.contains(":")) {
|
||||
throw new IllegalArgumentException("Invalid " + label + " '" + value + "'.");
|
||||
}
|
||||
return clean;
|
||||
}
|
||||
|
||||
private static String escape(String value) {
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
record WriteResult(String targetDimension, String packName, String packDimension, File dimensionFile, File typeFile, File packMetaFile) {
|
||||
}
|
||||
|
||||
private record ResourceTarget(String namespace, String path) {
|
||||
static ResourceTarget parse(String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("Missing dimension id.");
|
||||
}
|
||||
|
||||
String normalized = value.trim().toLowerCase(Locale.ROOT);
|
||||
String namespace = DEFAULT_NAMESPACE;
|
||||
String path = normalized;
|
||||
int colon = normalized.indexOf(':');
|
||||
if (colon >= 0) {
|
||||
namespace = normalized.substring(0, colon);
|
||||
path = normalized.substring(colon + 1);
|
||||
}
|
||||
|
||||
if (!namespace.matches("[a-z0-9_.-]+") || !path.matches("[a-z0-9_./-]+") || path.startsWith("/") || path.endsWith("/") || path.contains("..")) {
|
||||
throw new IllegalArgumentException("Invalid dimension id '" + value + "'. Use name or namespace:path.");
|
||||
}
|
||||
return new ResourceTarget(namespace, path);
|
||||
}
|
||||
|
||||
String id() {
|
||||
return namespace + ":" + path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"type": "irisworldgen:overworld",
|
||||
"generator": {
|
||||
"type": "irisworldgen:iris",
|
||||
"dimension": "overworld",
|
||||
"biome_source": {
|
||||
"type": "minecraft:fixed",
|
||||
"biome": "minecraft:plains"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,10 @@ Properties rootProperties = new Properties()
|
||||
file('../../gradle.properties').withInputStream { InputStream stream -> rootProperties.load(stream) }
|
||||
String irisVersion = providers.gradleProperty('irisVersion').getOrElse(rootProperties.getProperty('irisVersion', '4.0.0-26.1'))
|
||||
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse(rootProperties.getProperty('minecraftVersion', '26.1.2'))
|
||||
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.1.2.75')
|
||||
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse(rootProperties.getProperty('neoForgeVersion', '26.1.2.75'))
|
||||
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||
}
|
||||
|
||||
group = 'art.arcane'
|
||||
version = irisVersion
|
||||
@@ -168,7 +171,10 @@ processResources {
|
||||
}
|
||||
|
||||
tasks.named('shadowJar', ShadowJar).configure {
|
||||
archiveFileName.set("Iris-${project.version}+mc${minecraftVersion}-neoforge.jar")
|
||||
doFirst {
|
||||
delete(layout.buildDirectory.file("libs/Iris-${project.version}+mc${minecraftVersion}-neoforge.jar"))
|
||||
}
|
||||
archiveFileName.set(irisArtifactName('NeoForge', "${minecraftVersion}+${neoForgeVersion}"))
|
||||
configurations = [project.configurations.named('bundle').get()]
|
||||
mergeServiceFiles()
|
||||
exclude('META-INF/maven/**')
|
||||
|
||||
@@ -51,7 +51,6 @@ public final class IrisNeoForgeBootstrap {
|
||||
|
||||
public IrisNeoForgeBootstrap(IEventBus modBus) {
|
||||
ModdedEngineBootstrap.initialize(new NeoForgeModdedLoader());
|
||||
art.arcane.iris.modded.ModdedPackInstaller.ensureDefaultPack(ModdedEngineBootstrap.loader().configDir());
|
||||
String modVersion = ModList.get().getModContainerById("irisworldgen")
|
||||
.map((ModContainer container) -> container.getModInfo().getVersion().toString())
|
||||
.orElse("unknown");
|
||||
|
||||
@@ -6,7 +6,7 @@ license = "GPL-3.0"
|
||||
modId = "irisworldgen"
|
||||
version = "${version}"
|
||||
displayName = "Iris"
|
||||
description = "Iris World Generation Engine (NeoForge adapter - native chunk generator, overworld override datapack, engine lifecycle)"
|
||||
description = "Iris World Generation Engine (NeoForge adapter - native chunk generator, explicit Iris world datapack workflow, engine lifecycle)"
|
||||
authors = "Arcane Arts (Volmit Software)"
|
||||
|
||||
[[dependencies.irisworldgen]]
|
||||
|
||||
+69
-12
@@ -48,9 +48,28 @@ project(':core') {
|
||||
version = rootProject.version
|
||||
}
|
||||
String minecraftVersion = providers.gradleProperty('minecraftVersion').getOrElse('26.1.2')
|
||||
String fabricLoaderVersion = providers.gradleProperty('fabricLoaderVersion').getOrElse('0.19.3')
|
||||
String forgeVersion = providers.gradleProperty('forgeVersion').getOrElse('26.1.2-64.0.9')
|
||||
String neoForgeVersion = providers.gradleProperty('neoForgeVersion').getOrElse('26.1.2.75')
|
||||
String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate')
|
||||
.orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT')
|
||||
.get()
|
||||
Closure<String> loaderDisplayVersion = { String loaderVersion ->
|
||||
String coordinatePrefix = "${minecraftVersion}-"
|
||||
if (loaderVersion.startsWith(coordinatePrefix)) {
|
||||
return loaderVersion.substring(coordinatePrefix.length())
|
||||
}
|
||||
|
||||
return loaderVersion
|
||||
}
|
||||
Closure<String> irisArtifactName = { String platform, String targetVersion ->
|
||||
return "Iris v${project.version} [${platform}] ${targetVersion}.jar"
|
||||
}
|
||||
String bukkitArtifactName = irisArtifactName('CraftBukkit', minecraftVersion)
|
||||
String fabricArtifactName = irisArtifactName('Fabric', "${minecraftVersion}+${loaderDisplayVersion(fabricLoaderVersion)}")
|
||||
String forgeArtifactName = irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}")
|
||||
String neoForgeArtifactName = irisArtifactName('NeoForge', "${minecraftVersion}+${loaderDisplayVersion(neoForgeVersion)}")
|
||||
extensions.extraProperties.set('bukkitArtifactName', bukkitArtifactName)
|
||||
|
||||
apply plugin: ApiGenerator
|
||||
|
||||
@@ -115,64 +134,102 @@ tasks.named('jar', Jar).configure {
|
||||
inputs.files(included)
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
from(jarJar, provider { included.resolve().collect { zipTree(it) } })
|
||||
archiveFileName.set("Iris-${project.version}.jar")
|
||||
doFirst {
|
||||
delete(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
|
||||
}
|
||||
archiveFileName.set(bukkitArtifactName)
|
||||
}
|
||||
|
||||
tasks.register('iris', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('jar')
|
||||
from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
|
||||
from(layout.buildDirectory.file("libs/${bukkitArtifactName}"))
|
||||
into(layout.buildDirectory)
|
||||
doFirst {
|
||||
delete(layout.buildDirectory.file("Iris-${project.version}.jar"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('buildBukkit', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('jar')
|
||||
from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
|
||||
rename { "Iris-${project.version}+mc${minecraftVersion}.jar" }
|
||||
from(layout.buildDirectory.file("libs/${bukkitArtifactName}"))
|
||||
into(layout.projectDirectory.dir('dist'))
|
||||
doFirst {
|
||||
delete(layout.projectDirectory.file("dist/Iris-${project.version}+mc${minecraftVersion}.jar"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('fabricJar', Exec) {
|
||||
group = 'iris'
|
||||
workingDir = layout.projectDirectory.dir('adapters/fabric').asFile
|
||||
String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew'
|
||||
commandLine(layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain')
|
||||
commandLine(
|
||||
layout.projectDirectory.file(wrapperScript).asFile.absolutePath,
|
||||
'shadowJar',
|
||||
'--console=plain',
|
||||
"-PirisVersion=${project.version}",
|
||||
"-PminecraftVersion=${minecraftVersion}",
|
||||
"-PfabricLoaderVersion=${fabricLoaderVersion}"
|
||||
)
|
||||
}
|
||||
|
||||
tasks.register('buildFabric', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('fabricJar')
|
||||
from(layout.projectDirectory.file("adapters/fabric/build/libs/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
|
||||
from(layout.projectDirectory.file("adapters/fabric/build/libs/${fabricArtifactName}"))
|
||||
into(layout.projectDirectory.dir('dist'))
|
||||
doFirst {
|
||||
delete(layout.projectDirectory.file("dist/Iris-${project.version}+mc${minecraftVersion}-fabric.jar"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('forgeJar', Exec) {
|
||||
group = 'iris'
|
||||
workingDir = layout.projectDirectory.dir('adapters/forge').asFile
|
||||
String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew'
|
||||
commandLine(layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain')
|
||||
commandLine(
|
||||
layout.projectDirectory.file(wrapperScript).asFile.absolutePath,
|
||||
'shadowJar',
|
||||
'--console=plain',
|
||||
"-PirisVersion=${project.version}",
|
||||
"-PminecraftVersion=${minecraftVersion}",
|
||||
"-PforgeVersion=${forgeVersion}"
|
||||
)
|
||||
}
|
||||
|
||||
tasks.register('buildForge', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('forgeJar')
|
||||
from(layout.projectDirectory.file("adapters/forge/build/libs/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
|
||||
from(layout.projectDirectory.file("adapters/forge/build/libs/${forgeArtifactName}"))
|
||||
into(layout.projectDirectory.dir('dist'))
|
||||
doFirst {
|
||||
delete(layout.projectDirectory.file("dist/Iris-${project.version}+mc${minecraftVersion}-forge.jar"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('neoforgeJar', Exec) {
|
||||
group = 'iris'
|
||||
workingDir = layout.projectDirectory.dir('adapters/neoforge').asFile
|
||||
String wrapperScript = System.getProperty('os.name').toLowerCase().contains('windows') ? 'gradlew.bat' : 'gradlew'
|
||||
commandLine(layout.projectDirectory.file(wrapperScript).asFile.absolutePath, 'shadowJar', '--console=plain')
|
||||
commandLine(
|
||||
layout.projectDirectory.file(wrapperScript).asFile.absolutePath,
|
||||
'shadowJar',
|
||||
'--console=plain',
|
||||
"-PirisVersion=${project.version}",
|
||||
"-PminecraftVersion=${minecraftVersion}",
|
||||
"-PneoForgeVersion=${neoForgeVersion}"
|
||||
)
|
||||
}
|
||||
|
||||
tasks.register('buildNeoforge', Copy) {
|
||||
group = 'iris'
|
||||
dependsOn('neoforgeJar')
|
||||
from(layout.projectDirectory.file("adapters/neoforge/build/libs/Iris-${project.version}+mc${minecraftVersion}-neoforge.jar"))
|
||||
from(layout.projectDirectory.file("adapters/neoforge/build/libs/${neoForgeArtifactName}"))
|
||||
into(layout.projectDirectory.dir('dist'))
|
||||
doFirst {
|
||||
delete(layout.projectDirectory.file("dist/Iris-${project.version}+mc${minecraftVersion}-neoforge.jar"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('buildAll') {
|
||||
@@ -322,7 +379,7 @@ void registerCustomOutputTask(String name, String path) {
|
||||
group = 'development'
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn('iris')
|
||||
from(layout.buildDirectory.file("Iris-${project.version}.jar"))
|
||||
from(layout.buildDirectory.file((String) project.ext.bukkitArtifactName))
|
||||
into(file(path))
|
||||
rename { String ignored -> 'Iris.jar' }
|
||||
}
|
||||
@@ -337,7 +394,7 @@ void registerCustomOutputTaskUnix(String name, String path) {
|
||||
group = 'development'
|
||||
outputs.upToDateWhen { false }
|
||||
dependsOn('iris')
|
||||
from(layout.buildDirectory.file("Iris-${project.version}.jar"))
|
||||
from(layout.buildDirectory.file((String) project.ext.bukkitArtifactName))
|
||||
into(file(path))
|
||||
rename { String ignored -> 'Iris.jar' }
|
||||
}
|
||||
|
||||
@@ -90,12 +90,21 @@ public class DataFixerV1217 extends DataFixerV1213 {
|
||||
@Override
|
||||
public JSONObject fixCustomBiome(IrisBiomeCustom biome, JSONObject json) {
|
||||
json = super.fixCustomBiome(biome, json);
|
||||
var effects = json.getJSONObject("effects");
|
||||
var attributes = new JSONObject();
|
||||
JSONObject effects = json.getJSONObject("effects");
|
||||
JSONObject attributes = new JSONObject();
|
||||
|
||||
attributes.put("minecraft:visual/fog_color", effects.remove("fog_color"));
|
||||
attributes.put("minecraft:visual/sky_color", effects.remove("sky_color"));
|
||||
attributes.put("minecraft:visual/water_color", effects.remove("water_color"));
|
||||
attributes.put("minecraft:visual/water_fog_color", effects.remove("water_fog_color"));
|
||||
Object grassColor = effects.remove("grass_color");
|
||||
if (grassColor != null) {
|
||||
attributes.put("minecraft:visual/grass_color", grassColor);
|
||||
}
|
||||
Object foliageColor = effects.remove("foliage_color");
|
||||
if (foliageColor != null) {
|
||||
attributes.put("minecraft:visual/foliage_color", foliageColor);
|
||||
}
|
||||
|
||||
JSONObject particle = (JSONObject) effects.remove("particle");
|
||||
if (particle != null) {
|
||||
@@ -112,7 +121,7 @@ public class DataFixerV1217 extends DataFixerV1213 {
|
||||
public void fixDimension(Dimension dimension, JSONObject json) {
|
||||
super.fixDimension(dimension, json);
|
||||
|
||||
var attributes = new JSONObject();
|
||||
JSONObject attributes = new JSONObject();
|
||||
if ((Boolean) json.remove("ultrawarm")) {
|
||||
attributes.put("minecraft:gameplay/water_evaporates", true);
|
||||
attributes.put("minecraft:gameplay/fast_lava", true);
|
||||
@@ -138,8 +147,10 @@ public class DataFixerV1217 extends DataFixerV1213 {
|
||||
attributes.put("minecraft:gameplay/piglins_zombify", !(Boolean) json.remove("piglin_safe"));
|
||||
attributes.put("minecraft:gameplay/can_start_raid", json.remove("has_raids"));
|
||||
|
||||
var cloud_height = json.remove("cloud_height");
|
||||
if (cloud_height != null) attributes.put("minecraft:visual/cloud_height", cloud_height);
|
||||
Object cloudHeight = json.remove("cloud_height");
|
||||
if (cloudHeight != null) {
|
||||
attributes.put("minecraft:visual/cloud_height", cloudHeight);
|
||||
}
|
||||
|
||||
boolean natural = (Boolean) json.remove("natural");
|
||||
attributes.put("minecraft:gameplay/nether_portal_spawns_piglin", natural);
|
||||
@@ -152,7 +163,7 @@ public class DataFixerV1217 extends DataFixerV1213 {
|
||||
json.put("attributes", attributes);
|
||||
|
||||
json.remove("effects");
|
||||
var defaults = new JSONObject(DIMENSIONS.get(dimension));
|
||||
JSONObject defaults = new JSONObject(DIMENSIONS.get(dimension));
|
||||
merge(json, defaults);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedActuator;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeCustom;
|
||||
import art.arcane.iris.platform.bukkit.BukkitBiome;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
@@ -53,15 +54,27 @@ public class IrisBiomeActuator extends EngineAssignedActuator<PlatformBiome> {
|
||||
for (int zf = 0; zf < h.getDepth(); zf++) {
|
||||
ib = context.getBiome().get(xf, zf);
|
||||
MatterBiomeInject matter;
|
||||
PlatformBiome biome;
|
||||
|
||||
if (ib.isCustom()) {
|
||||
IrisBiomeCustom custom = ib.getCustomBiome(rng, x, 0, z);
|
||||
matter = BiomeInjectMatter.get(IrisPlatforms.get().biomeWriter().biomeIdFor(getDimension().getLoadKey() + ":" + custom.getId()));
|
||||
String key = getDimension().getLoadKey() + ":" + custom.getId();
|
||||
biome = IrisPlatforms.get().registries().biome(key);
|
||||
matter = BiomeInjectMatter.get(IrisPlatforms.get().biomeWriter().biomeIdFor(key));
|
||||
} else {
|
||||
Biome v = ib.getSkyBiome(rng, x, 0, z);
|
||||
PlatformBiome fallback = BukkitBiome.of(v);
|
||||
PlatformBiome resolved = IrisPlatforms.get().registries().biome(fallback.key());
|
||||
biome = resolved == null ? fallback : resolved;
|
||||
matter = BiomeInjectMatter.get(v);
|
||||
}
|
||||
|
||||
if (biome != null) {
|
||||
for (int yf = 0; yf < h.getHeight(); yf++) {
|
||||
h.set(xf, yf, zf, biome);
|
||||
}
|
||||
}
|
||||
|
||||
getEngine().getMantle().getMantle().set(x + xf, 0, z + zf, matter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ public class IrisBiomeCustom {
|
||||
private int parseColor(String c) {
|
||||
String v = (c.startsWith("#") ? c : "#" + c).trim();
|
||||
try {
|
||||
return Color.decode(v).getRGB();
|
||||
return Color.decode(v).getRGB() & 0x00FFFFFF;
|
||||
} catch (Throwable e) {
|
||||
IrisLogging.reportError(e);
|
||||
IrisLogging.error("Error Parsing '''color''', (" + c + ")");
|
||||
|
||||
@@ -25,3 +25,6 @@ nmsTools.repo-url=https://repo.codemc.org/repository/nms/
|
||||
nmsTools.specialSourceVersion=1.11.4
|
||||
irisVersion=4.0.0-26.1
|
||||
minecraftVersion=26.1.2
|
||||
fabricLoaderVersion=0.19.3
|
||||
forgeVersion=26.1.2-64.0.9
|
||||
neoForgeVersion=26.1.2.75
|
||||
|
||||
Reference in New Issue
Block a user