d
This commit is contained in:
Brian Neumann-Fopiano
2026-06-13 17:07:27 -04:00
parent f482b8ef7e
commit ad6732812e
19 changed files with 1050 additions and 337 deletions
+4
View File
@@ -19,3 +19,7 @@ dist/
core/plugins/
adapters/bukkit/plugin/plugins/
adapters/*/run/
.codegraph/
plans/
@@ -21,10 +21,7 @@ package art.arcane.iris;
import art.arcane.iris.engine.IrisWorldManager;
import art.arcane.iris.engine.framework.EngineWorldManagerProvider;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import art.arcane.iris.core.splash.IrisSplashPackScanner;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.ServerConfigurator;
@@ -58,8 +55,10 @@ import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.core.safeguard.IrisSafeguard;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisPlatforms;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.spi.LogLevel;
import art.arcane.volmlib.integration.ReloadAware;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
@@ -235,7 +234,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
getSender().sendMessage(string);
} catch (Throwable e) {
try {
instance.getLogger().info(instance.getTag() + string.replaceAll("(<([^>]+)>)", ""));
instance.getLogger().info(instance.getTag() + IrisLogging.clean(string));
} catch (Throwable inner) {
System.err.println("[Iris] Failed to emit log message: " + inner.getMessage());
inner.printStackTrace(System.err);
@@ -377,19 +376,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
private static String safeFormat(String format, Object... args) {
if (format == null) {
return "null";
}
if (args == null || args.length == 0) {
return format;
}
try {
return String.format(format, args);
} catch (IllegalFormatException ignored) {
return format;
}
return IrisLogging.format(format, args);
}
public static void later(NastyRunnable object) {
@@ -555,14 +542,7 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
BukkitPlatform.hostPlugin(this);
BukkitPlatform.hostConsoleSender(Iris::getSender);
BukkitPlatform.hostBridge(new BukkitPlatform.HostBridge(
(level, message) -> {
switch (level) {
case DEBUG -> Iris.debug(message);
case INFO -> Iris.info(message);
case WARN -> Iris.warn(message);
case ERROR -> Iris.error(message);
}
},
Iris::bridgeLog,
Iris::msg,
Iris::reportError,
(event) -> Iris.callEvent((org.bukkit.event.Event) event),
@@ -574,6 +554,16 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
SlimJar.load();
}
private static void bridgeLog(LogLevel level, String message) {
LogLevel target = level == null ? LogLevel.INFO : level;
switch (target) {
case DEBUG -> Iris.debug(message);
case INFO -> Iris.info(message);
case WARN -> Iris.warn(message);
case ERROR -> Iris.error(message);
}
}
private void enable() {
PaperLibBootstrap.install();
SimdSupport.install();
@@ -1279,49 +1269,24 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
}
static List<SplashPackMetadata> collectSplashPacks(File packFolder) {
if (packFolder == null || !packFolder.isDirectory()) {
List<IrisSplashPackScanner.SplashPackMetadata> scanned = IrisSplashPackScanner.collect(packFolder, Iris::reportError);
if (scanned.isEmpty()) {
return Collections.emptyList();
}
File[] folders = packFolder.listFiles(File::isDirectory);
if (folders == null || folders.length == 0) {
return Collections.emptyList();
List<SplashPackMetadata> packs = new ArrayList<>(scanned.size());
for (IrisSplashPackScanner.SplashPackMetadata metadata : scanned) {
packs.add(new SplashPackMetadata(metadata.name(), metadata.version()));
}
List<SplashPackMetadata> packs = new ArrayList<>();
for (File folder : folders) {
SplashPackMetadata metadata = readSplashPack(folder);
if (metadata != null) {
packs.add(metadata);
}
}
packs.sort(Comparator.comparing(SplashPackMetadata::name));
return packs;
}
static SplashPackMetadata readSplashPack(File pack) {
if (pack == null || !pack.isDirectory()) {
return null;
}
String dimName = pack.getName();
File dimensionFile = new File(pack, "dimensions/" + dimName + ".json");
if (!dimensionFile.isFile()) {
return null;
}
try (FileReader r = new FileReader(dimensionFile)) {
JsonObject json = JsonParser.parseReader(r).getAsJsonObject();
if (!json.has("version")) {
return null;
}
return new SplashPackMetadata(dimName, json.get("version").getAsString());
} catch (IOException | JsonParseException ex) {
reportError("Failed to read splash metadata for dimension pack \"" + dimName + "\".", ex);
IrisSplashPackScanner.SplashPackMetadata metadata = IrisSplashPackScanner.read(pack, Iris::reportError);
if (metadata == null) {
return null;
}
return new SplashPackMetadata(metadata.name(), metadata.version());
}
private void printPack(SplashPackMetadata pack) {
@@ -20,6 +20,7 @@ package art.arcane.iris.fabric;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedParityProbe;
import art.arcane.iris.modded.ModdedWorldCheck;
import art.arcane.iris.modded.ModdedWorldEngines;
@@ -49,12 +50,8 @@ import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.BlockHitResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class IrisFabricBootstrap implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
@Override
public void onInitialize() {
ModdedEngineBootstrap.initialize(new FabricModdedLoader());
@@ -65,12 +62,12 @@ public final class IrisFabricBootstrap implements ModInitializer {
String minecraftVersion = loader.getModContainer("minecraft")
.map((ModContainer container) -> container.getMetadata().getVersion().getFriendlyString())
.orElse("unknown");
LOGGER.info("Iris {} bootstrapping on Minecraft {} (Fabric)", modVersion, minecraftVersion);
ModdedIrisLog.info("Iris " + modVersion + " bootstrapping on Minecraft " + minecraftVersion + " (Fabric)");
ModdedEngineBootstrap.selfTest(IrisFabricBootstrap.class.getClassLoader());
ModdedEngineBootstrap.bind();
Registry.register(BuiltInRegistries.CHUNK_GENERATOR, Identifier.fromNamespaceAndPath("irisworldgen", "iris"), IrisModdedChunkGenerator.CODEC);
LOGGER.info("Iris chunk generator registered as irisworldgen:iris");
ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris");
ServerLifecycleEvents.SERVER_STOPPING.register((MinecraftServer server) -> {
ModdedObjectUndo.clearAll();
ModdedWandService.clearAll();
@@ -85,13 +82,13 @@ public final class IrisFabricBootstrap implements ModInitializer {
String parity = System.getProperty("iris.parity");
if (parity != null) {
LOGGER.info("Iris parity probe armed: {}", parity);
ModdedIrisLog.info("Iris parity probe armed: " + parity);
ModdedParityProbe.schedule(parity);
}
String worldCheck = System.getProperty("iris.worldcheck");
if (worldCheck != null) {
LOGGER.info("Iris world check armed");
ModdedIrisLog.info("Iris world check armed");
ModdedWorldCheck.schedule();
}
}
@@ -20,6 +20,7 @@ package art.arcane.iris.forge;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedParityProbe;
import art.arcane.iris.modded.ModdedWorldCheck;
import art.arcane.iris.modded.ModdedWorldEngines;
@@ -42,20 +43,16 @@ import net.minecraftforge.fml.loading.VersionInfo;
import net.minecraftforge.registries.DeferredRegister;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod("irisworldgen")
public final class IrisForgeBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
public IrisForgeBootstrap(FMLJavaModLoadingContext context) {
ModdedEngineBootstrap.initialize(new ForgeModdedLoader());
String modVersion = ModList.getModContainerById("irisworldgen")
.map((ModContainer container) -> container.getModInfo().getVersion().toString())
.orElse("unknown");
VersionInfo versionInfo = FMLLoader.versionInfo();
LOGGER.info("Iris {} bootstrapping on Minecraft {} (Forge {})", modVersion, versionInfo.mcVersion(), versionInfo.forgeVersion());
ModdedIrisLog.info("Iris " + modVersion + " bootstrapping on Minecraft " + versionInfo.mcVersion() + " (Forge " + versionInfo.forgeVersion() + ")");
ModdedEngineBootstrap.selfTest(IrisForgeBootstrap.class.getClassLoader());
ModdedEngineBootstrap.bind();
@@ -63,7 +60,7 @@ public final class IrisForgeBootstrap {
DeferredRegister<MapCodec<? extends ChunkGenerator>> chunkGenerators = DeferredRegister.create(Registries.CHUNK_GENERATOR, "irisworldgen");
chunkGenerators.register("iris", () -> IrisModdedChunkGenerator.CODEC);
chunkGenerators.register(context.getModBusGroup());
LOGGER.info("Iris chunk generator registered as irisworldgen:iris");
ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris");
ServerStoppingEvent.BUS.addListener((ServerStoppingEvent event) -> {
ModdedObjectUndo.clearAll();
@@ -79,13 +76,13 @@ public final class IrisForgeBootstrap {
String parity = System.getProperty("iris.parity");
if (parity != null) {
LOGGER.info("Iris parity probe armed: {}", parity);
ModdedIrisLog.info("Iris parity probe armed: " + parity);
ModdedParityProbe.schedule(parity);
}
String worldCheck = System.getProperty("iris.worldcheck");
if (worldCheck != null) {
LOGGER.info("Iris world check armed");
ModdedIrisLog.info("Iris world check armed");
ModdedWorldCheck.schedule();
}
}
@@ -82,7 +82,7 @@ public final class ModdedEngineBootstrap {
throw new IllegalStateException("Iris core self-test failed: only " + loadedClasses + " of " + CORE_SELF_TEST_CLASSES.length + " engine classes initialized");
}
LOGGER.info("Iris core loaded ({} classes ok)", loadedClasses);
ModdedIrisLog.info("Iris core loaded (" + loadedClasses + " classes ok)");
}
public static ModdedPlatform bind() {
@@ -105,6 +105,7 @@ public final class ModdedEngineBootstrap {
IrisServices.register(PreservationRegistry.class, new InertPreservation());
IrisServices.register(EngineWorldManagerProvider.class, (EngineWorldManagerProvider) (Engine engine) -> new InertWorldManager());
platform = created;
ModdedIrisSplash.print(boundLoader);
return created;
}
}
@@ -0,0 +1,95 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.LogLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ModdedIrisLog {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static volatile boolean DEBUG_SETTING_WARNING_LOGGED;
private ModdedIrisLog() {
}
public static void log(LogLevel level, String message) {
LogLevel target = level == null ? LogLevel.INFO : level;
switch (target) {
case DEBUG -> debug(message);
case INFO -> info(message);
case WARN -> warn(message);
case ERROR -> error(message);
}
}
public static void debug(String message) {
if (!debugEnabled()) {
return;
}
LOGGER.debug(clean(message));
}
public static void info(String message) {
LOGGER.info(clean(message));
}
public static void warn(String message) {
LOGGER.warn(clean(message));
}
public static void error(String message) {
LOGGER.error(clean(message));
}
public static void error(String message, Throwable error) {
if (error == null) {
error(message);
return;
}
LOGGER.error(clean(message), error);
}
public static String clean(String message) {
return IrisLogging.clean(message);
}
private static boolean debugEnabled() {
try {
IrisSettings settings = IrisSettings.settings != null ? IrisSettings.settings : IrisSettings.get();
return settings != null && settings.getGeneral() != null && settings.getGeneral().isDebug();
} catch (Throwable error) {
warnDebugSetting(error);
return false;
}
}
private static void warnDebugSetting(Throwable error) {
if (DEBUG_SETTING_WARNING_LOGGED) {
return;
}
DEBUG_SETTING_WARNING_LOGGED = true;
LOGGER.warn("Iris debug logging setting could not be read", error);
}
}
@@ -0,0 +1,121 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded;
import art.arcane.iris.BuildConstants;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.splash.IrisSplashPackScanner;
import art.arcane.iris.core.splash.IrisSplashPackScanner.SplashPackMetadata;
import art.arcane.iris.core.splash.IrisSplashRenderer;
import art.arcane.iris.spi.IrisLogging;
import java.io.File;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
public final class ModdedIrisSplash {
private ModdedIrisSplash() {
}
public static void print(ModdedLoader loader) {
printPacks(loader);
if (isLogoEnabled()) {
printLogo(loader);
}
}
private static void printPacks(ModdedLoader loader) {
File packFolder = loader.configDir().resolve("irisworldgen").resolve("packs").toFile();
List<SplashPackMetadata> packs = IrisSplashPackScanner.collect(packFolder, IrisLogging::reportError);
if (packs.isEmpty()) {
return;
}
IrisLogging.info("Custom Dimensions: " + packs.size());
for (SplashPackMetadata pack : packs) {
IrisLogging.info(" " + pack.name() + " v" + pack.version());
}
}
private static void printLogo(ModdedLoader loader) {
String padding = " ".repeat(4);
String version = loader.modVersion();
String releaseTrain = getReleaseTrain(version);
String startupDate = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
int javaVersion = getJavaVersion();
String[] splash = IrisSplashRenderer.renderPlain();
String[] info = new String[]{
"",
" Iris, Dimension Engine [" + releaseTrain + " RC.1.1.6]",
" Version: " + version,
" By: Volmit Software (Arcane Arts)",
" Server: " + loader.platformName() + " / Minecraft " + loader.minecraftVersion(),
" Java: " + javaVersion + " | Date: " + startupDate,
" Commit: " + BuildConstants.COMMIT + "/" + BuildConstants.ENVIRONMENT,
"",
"",
"",
""
};
StringBuilder builder = new StringBuilder("\n\n");
for (int i = 0; i < splash.length; i++) {
builder.append(padding).append(splash[i]).append(info[i]).append('\n');
}
IrisLogging.info(builder.toString());
}
private static boolean isLogoEnabled() {
try {
return IrisSettings.get().getGeneral().isSplashLogoStartup();
} catch (Throwable error) {
IrisLogging.warn("Iris splash setting could not be read: " + error.getClass().getSimpleName());
return true;
}
}
private static int getJavaVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf('.');
if (dot != -1) {
version = version.substring(0, dot);
}
}
return Integer.parseInt(version);
}
private static String getReleaseTrain(String version) {
String value = version == null ? "unknown" : version;
int suffixIndex = value.indexOf('-');
if (suffixIndex >= 0) {
value = value.substring(0, suffixIndex);
}
String[] split = value.split("\\.");
if (split.length >= 2) {
return split[0] + "." + split[1];
}
return value;
}
}
@@ -26,14 +26,11 @@ import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.spi.PlatformStructureHooks;
import net.minecraft.server.MinecraftServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.function.Consumer;
public final class ModdedPlatform implements IrisPlatform {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static volatile Consumer<Throwable> ERROR_SINK = null;
private final ModdedLoader loader;
@@ -135,17 +132,12 @@ public final class ModdedPlatform implements IrisPlatform {
@Override
public void log(LogLevel level, String message) {
switch (level) {
case DEBUG -> LOGGER.debug(message);
case INFO -> LOGGER.info(message);
case WARN -> LOGGER.warn(message);
case ERROR -> LOGGER.error(message);
}
ModdedIrisLog.log(level, message);
}
@Override
public void msg(String message) {
LOGGER.info(message);
ModdedIrisLog.info(message);
}
@Override
@@ -156,7 +148,7 @@ public final class ModdedPlatform implements IrisPlatform {
return;
}
if (error != null) {
LOGGER.error("Iris reported error", error);
ModdedIrisLog.error("Iris reported error", error);
}
}
@@ -28,6 +28,7 @@ import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedLoader;
import art.arcane.iris.modded.ModdedPackInstaller;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.math.Position2;
import com.mojang.brigadier.CommandDispatcher;
@@ -42,6 +43,7 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.Component;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.MinecraftServer;
@@ -84,7 +86,7 @@ public final class IrisModdedCommands {
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");
IrisLogging.info("Iris /iris command tree registered");
}
private static LiteralArgumentBuilder<CommandSourceStack> rootTree() {
@@ -322,7 +324,7 @@ public final class IrisModdedCommands {
}
private static int pregenStatus(CommandSourceStack source) {
String status = ModdedPregenJob.status();
Component status = ModdedPregenJob.statusComponent();
if (status == null) {
fail(source, "No active pregeneration task.");
return 0;
@@ -747,6 +749,10 @@ public final class IrisModdedCommands {
ModdedCommandFeedback.ok(source, message);
}
static void ok(CommandSourceStack source, Component component) {
ModdedCommandFeedback.ok(source, component);
}
static void fail(CommandSourceStack source, String message) {
ModdedCommandFeedback.fail(source, message);
}
@@ -20,7 +20,12 @@ 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 net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
@@ -31,6 +36,25 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
final class ModdedCommandFeedback {
static final int IRIS = 0x1BB19E;
static final int HEADER_A = 0x34EB6B;
static final int HEADER_B = 0x32BFAD;
static final int BACK = 0x6FE98F;
static final int DARK_GREEN = 0x46826A;
static final int DESCRIPTION_ICON = 0x3FE05A;
static final int DESCRIPTION = 0x6AD97D;
static final int USAGE_ICON = 0xBBE03F;
static final int USAGE = 0xA8E0A2;
static final int EXAMPLE_ICON = 0xC2F7D2;
static final int PARAMETER = 0x5EF288;
static final int PARAMETER_ALT = 0x32BFAD;
static final int OPTIONAL = 0x4F4F4F;
static final int CATEGORY = 0x9DE5B6;
static final int REQUIRED = 0xDB4321;
static final int REQUIRED_TEXT = 0xFAA796;
static final int HOVER_TYPE = 0x8AD9AF;
static final int VALUE = 0xC2F7D2;
static final int PAGE_LINE_LENGTH = 75;
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<>();
@@ -44,6 +68,11 @@ final class ModdedCommandFeedback {
playSuccess(source);
}
static void ok(CommandSourceStack source, Component component) {
source.sendSuccess(() -> component, false);
playSuccess(source);
}
static void fail(CommandSourceStack source, String message) {
source.sendFailure(Component.literal(message).withStyle(ChatFormatting.RED));
playFailure(source);
@@ -53,6 +82,61 @@ final class ModdedCommandFeedback {
source.sendSuccess(() -> component, false);
}
static void clear(CommandSourceStack source) {
if (source.getPlayer() != null) {
send(source, Component.literal("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"));
}
}
static MutableComponent header(String title) {
MutableComponent header = Component.empty();
header.append(text(" ".repeat(18), HEADER_A, false, true));
header.append(text(" " + title + " ", IRIS, true, false));
header.append(text(" ".repeat(18), HEADER_B, false, true));
return header;
}
static MutableComponent footer() {
return text(" ".repeat(PAGE_LINE_LENGTH), HEADER_B, false, true);
}
static MutableComponent text(String value, int color) {
return text(value, color, false, false);
}
static MutableComponent text(String value, int color, boolean bold, boolean strikethrough) {
return Component.literal(value).withStyle((Style style) -> {
Style next = style.withColor(TextColor.fromRgb(color));
if (bold) {
next = next.withBold(true);
}
if (strikethrough) {
next = next.withStrikethrough(true);
}
return next;
});
}
static MutableComponent button(String label, String command, String hover, boolean runCommand) {
ClickEvent clickEvent = runCommand ? new ClickEvent.RunCommand(command) : new ClickEvent.SuggestCommand(command);
MutableComponent hoverText = text(hover, DESCRIPTION);
return text(label, PARAMETER_ALT, true, false).withStyle((Style style) -> style
.withClickEvent(clickEvent)
.withHoverEvent(new HoverEvent.ShowText(hoverText)));
}
static MutableComponent progressBar(double percent, int width) {
double clamped = Math.max(0D, Math.min(100D, percent));
int filled = (int) Math.round((clamped / 100D) * width);
MutableComponent bar = Component.empty();
bar.append(text("[", DARK_GREEN));
for (int i = 0; i < width; i++) {
bar.append(text(i < filled ? "|" : "·", i < filled ? PARAMETER : OPTIONAL));
}
bar.append(text("]", DARK_GREEN));
return bar;
}
static void tab(CommandSourceStack source) {
ServerPlayer player = source.getPlayer();
if (player == null || !claim(TAB_SOUNDS, player.getUUID(), TAB_SOUND_COOLDOWN_MS)) {
@@ -18,17 +18,32 @@
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.BACK;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.CATEGORY;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.DARK_GREEN;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.DESCRIPTION;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.DESCRIPTION_ICON;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.EXAMPLE_ICON;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.HOVER_TYPE;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.OPTIONAL;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.PARAMETER;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.PARAMETER_ALT;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.REQUIRED;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.REQUIRED_TEXT;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.USAGE;
import static art.arcane.iris.modded.command.ModdedCommandFeedback.USAGE_ICON;
final class ModdedCommandHelp {
private static final Map<String, List<Entry>> SECTIONS = new LinkedHashMap<>();
@@ -89,7 +104,7 @@ final class ModdedCommandHelp {
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("open", "<pack> [seed]", "Open or prepare a dimension pack studio workflow"),
Entry.command("close", "", "Explain modded studio workflow"),
Entry.command("vscode", "", "Explain editor workflow"),
Entry.command("update", "", "Explain workspace regeneration workflow"),
@@ -143,53 +158,187 @@ final class ModdedCommandHelp {
return 0;
}
sendHeader(source, normalized.isEmpty() ? "Iris Commands" : "/iris " + normalized);
ModdedCommandFeedback.clear(source);
sendHeader(source, normalized);
if (!normalized.isEmpty()) {
ModdedCommandFeedback.send(source, clickable(" < Back", "/iris", "/iris", "Back to Iris command groups", true));
ModdedCommandFeedback.send(source, backButton(normalized));
}
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.");
ModdedCommandFeedback.ok(source, footer());
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 void sendHeader(CommandSourceStack source, String path) {
String title = path.isEmpty() ? "/iris" : "/iris " + path;
ModdedCommandFeedback.send(source, ModdedCommandFeedback.header(title));
}
private static MutableComponent backButton(String path) {
String parent = parentPath(path);
String command = parent.isEmpty() ? "/iris" : "/iris help " + parent;
MutableComponent hover = Component.empty()
.append(text("Click to go back to ", DARK_GREEN))
.append(text(parent.isEmpty() ? "Iris" : parent, PARAMETER_ALT));
return text("〈 Back", BACK).withStyle((style) -> style
.withClickEvent(new ClickEvent.RunCommand(command))
.withHoverEvent(new HoverEvent.ShowText(hover)));
}
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));
MutableComponent row = Component.empty();
row.append(clickableCommand(path, entry));
row.append(nodes(entry));
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)));
private static MutableComponent clickableCommand(String path, Entry entry) {
String parent = path.isEmpty() ? "/iris" : "/iris " + path;
String command = parent + " " + entry.name();
String suggestion = entry.usage().isBlank() ? command : command + " " + entry.usage();
ClickEvent clickEvent = entry.group() ? new ClickEvent.RunCommand(command) : new ClickEvent.SuggestCommand(suggestion);
MutableComponent hover = entryHover(path, entry, suggestion);
MutableComponent display = Component.empty();
display.append(text(parent + " >", 0xFFFFFF));
display.append(text("", DARK_GREEN));
display.append(text(" " + entry.name(), PARAMETER_ALT));
return display.withStyle((style) -> style
.withClickEvent(clickEvent)
.withHoverEvent(new HoverEvent.ShowText(hover)));
}
private static MutableComponent nodes(Entry entry) {
if (entry.group()) {
return text(" - Category of Commands", CATEGORY);
}
return Component.literal(label).withStyle((style) -> style
.withColor(ChatFormatting.GREEN)
.withClickEvent(new ClickEvent.SuggestCommand(suggestion))
.withHoverEvent(new HoverEvent.ShowText(hoverText)));
List<String> tokens = usageTokens(entry.usage());
if (tokens.isEmpty()) {
return Component.empty();
}
MutableComponent nodes = Component.empty();
for (String token : tokens) {
nodes.append(Component.literal(" "));
nodes.append(parameter(token));
}
return nodes;
}
private static MutableComponent parameter(String token) {
String name = parameterName(token);
boolean required = token.startsWith("<");
MutableComponent title = Component.empty();
if (required) {
title.append(text("[", REQUIRED, true, false));
title.append(text(name, PARAMETER, false, false));
title.append(text("]", REQUIRED, true, false));
} else {
title.append(text("", OPTIONAL));
title.append(text(name, PARAMETER));
title.append(text("", OPTIONAL));
}
MutableComponent hover = Component.empty();
hover.append(text(name, PARAMETER));
hover.append(Component.literal("\n"));
hover.append(text("", DESCRIPTION_ICON));
hover.append(text("Command parameter", DESCRIPTION));
hover.append(Component.literal("\n"));
if (required) {
hover.append(text("", REQUIRED));
hover.append(text("This parameter is required.", REQUIRED_TEXT));
} else {
hover.append(text("", DESCRIPTION_ICON));
hover.append(text("This parameter is optional.", USAGE));
}
hover.append(Component.literal("\n"));
hover.append(text("", DARK_GREEN));
hover.append(text("This parameter is read as text by Brigadier.", HOVER_TYPE));
return title.withStyle((style) -> style.withHoverEvent(new HoverEvent.ShowText(hover)));
}
private static MutableComponent entryHover(String path, Entry entry, String suggestion) {
MutableComponent hover = Component.empty();
hover.append(text(names(entry), PARAMETER));
hover.append(Component.literal("\n"));
hover.append(text("", DESCRIPTION_ICON));
hover.append(text(entry.description(), DESCRIPTION));
hover.append(Component.literal("\n"));
hover.append(text("", USAGE_ICON));
if (entry.group()) {
hover.append(text("This is a command category. Click to run.", USAGE));
} else if (entry.usage().isBlank()) {
hover.append(text("There are no parameters. Click to type command.", USAGE));
} else {
hover.append(text("Hover over all of the parameters to learn more.", USAGE));
hover.append(Component.literal("\n"));
hover.append(text("", EXAMPLE_ICON));
hover.append(text(suggestion, PARAMETER));
}
String parent = path.isEmpty() ? "/iris" : "/iris " + path;
if (entry.aliases().length > 0) {
hover.append(Component.literal("\n"));
hover.append(text("Aliases: ", DARK_GREEN));
List<String> aliases = new ArrayList<>(entry.aliases().length);
for (String alias : entry.aliases()) {
aliases.add(parent + " " + alias);
}
hover.append(text(String.join(", ", aliases), PARAMETER_ALT));
}
return hover;
}
private static MutableComponent footer() {
return ModdedCommandFeedback.footer();
}
private static MutableComponent text(String value, int color) {
return ModdedCommandFeedback.text(value, color, false, false);
}
private static MutableComponent text(String value, int color, boolean bold, boolean strikethrough) {
return ModdedCommandFeedback.text(value, color, bold, strikethrough);
}
private static List<String> usageTokens(String usage) {
if (usage == null || usage.isBlank()) {
return List.of();
}
return List.of(usage.trim().split("\\s+"));
}
private static String names(Entry entry) {
if (entry.aliases().length == 0) {
return entry.name();
}
List<String> names = new ArrayList<>(entry.aliases().length + 1);
names.add(entry.name());
for (String alias : entry.aliases()) {
names.add(alias);
}
return String.join(", ", names);
}
private static String parameterName(String token) {
String name = token;
if ((name.startsWith("<") && name.endsWith(">")) || (name.startsWith("[") && name.endsWith("]"))) {
name = name.substring(1, name.length() - 1);
}
return name;
}
private static String parentPath(String path) {
int lastSpace = path.lastIndexOf(' ');
if (lastSpace <= 0) {
return "";
}
return path.substring(0, lastSpace);
}
private static String normalize(String path) {
@@ -24,6 +24,8 @@ import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.format.Form;
import art.arcane.volmlib.util.math.Position2;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -103,16 +105,63 @@ public final class ModdedPregenJob implements PregenListener {
if (job == null) {
return null;
}
long total = Math.max(1L, job.totalChunks);
double percent = ((double) job.generated / (double) total) * 100D;
return "Pregen " + job.dimension + ": "
+ Form.f(job.generated) + "/" + Form.f(job.totalChunks)
return job.statusText();
}
public static Component statusComponent() {
ModdedPregenJob job = ACTIVE.get();
if (job == null) {
return null;
}
double percent = job.percent();
MutableComponent status = Component.empty();
status.append(ModdedCommandFeedback.header("Iris Pregen"));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("Dimension ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(job.dimension, ModdedCommandFeedback.PARAMETER_ALT));
status.append(ModdedCommandFeedback.text(" · Method ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(job.method, ModdedCommandFeedback.PARAMETER));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.progressBar(percent, 32));
status.append(ModdedCommandFeedback.text(" " + String.format("%.1f", percent) + "%", ModdedCommandFeedback.USAGE));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("Chunks ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.f(job.generated) + "/" + Form.f(job.totalChunks), ModdedCommandFeedback.VALUE));
status.append(ModdedCommandFeedback.text(" · Speed ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.f((int) job.chunksPerSecond) + "/s", ModdedCommandFeedback.VALUE));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.text("ETA ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.duration(job.eta, 2), ModdedCommandFeedback.VALUE));
status.append(ModdedCommandFeedback.text(" · Elapsed ", ModdedCommandFeedback.DARK_GREEN));
status.append(ModdedCommandFeedback.text(Form.duration(job.elapsed, 2), ModdedCommandFeedback.VALUE));
if (job.pregenerator.paused()) {
status.append(ModdedCommandFeedback.text(" · PAUSED", ModdedCommandFeedback.REQUIRED, true, false));
}
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.button("Pause/Resume", "/iris pregen pause", "Toggle pregeneration pause state", true));
status.append(ModdedCommandFeedback.text(" ", ModdedCommandFeedback.OPTIONAL));
status.append(ModdedCommandFeedback.button("Stop", "/iris pregen stop", "Finish the current region and stop pregeneration", true));
status.append(Component.literal("\n"));
status.append(ModdedCommandFeedback.footer());
return status;
}
private String statusText() {
double percent = percent();
return "Pregen " + dimension + ": "
+ Form.f(generated) + "/" + Form.f(totalChunks)
+ " (" + String.format("%.1f", percent) + "%), "
+ Form.f((int) job.chunksPerSecond) + "/s"
+ ", ETA " + Form.duration(job.eta, 2)
+ ", elapsed " + Form.duration(job.elapsed, 2)
+ ", method " + job.method
+ (job.pregenerator.paused() ? ", PAUSED" : "");
+ Form.f((int) chunksPerSecond) + "/s"
+ ", ETA " + Form.duration(eta, 2)
+ ", elapsed " + Form.duration(elapsed, 2)
+ ", method " + method
+ (pregenerator.paused() ? ", PAUSED" : "");
}
private double percent() {
long total = Math.max(1L, totalChunks);
return ((double) generated / (double) total) * 100D;
}
@Override
@@ -38,11 +38,14 @@ import art.arcane.volmlib.util.json.JSONObject;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.Spiraler;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.slf4j.Logger;
@@ -102,12 +105,12 @@ public final class ModdedStudioCommands {
.then(Commands.argument("radius", IntegerArgumentType.integer(8, 1000))
.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(openTree("open"));
root.then(openTree("o"));
root.then(message("close", "There are no studio worlds on modded servers (/iris studio open prepares the pack workflow instead), so there is nothing to close."));
root.then(message("x", "There are no studio worlds on modded servers (/iris studio open prepares the pack workflow instead), so there is nothing to close."));
root.then(message("tpstudio", "There are no temporary Bukkit studio worlds on modded servers to teleport to; use /iris studio open <pack> to prepare the pack workflow instead."));
root.then(message("stp", "There are no temporary Bukkit studio worlds on modded servers to teleport to; use /iris studio open <pack> to prepare the pack workflow instead."));
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."));
@@ -128,6 +131,20 @@ public final class ModdedStudioCommands {
return root;
}
private static LiteralArgumentBuilder<CommandSourceStack> openTree(String name) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) -> openHelp(context.getSource()))
.then(Commands.argument("pack", StringArgumentType.word()).suggests(IrisModdedCommands.PACK_NAMES)
.executes((CommandContext<CommandSourceStack> context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), 1337L))
.then(Commands.argument("seed", LongArgumentType.longArg())
.executes((CommandContext<CommandSourceStack> context) -> open(context.getSource(), StringArgumentType.getString(context, "pack"), LongArgumentType.getLong(context, "seed")))));
}
private static int openHelp(CommandSourceStack source) {
IrisModdedCommands.fail(source, "Provide a dimension pack: /iris studio open <pack> [seed]");
return 0;
}
private static LiteralArgumentBuilder<CommandSourceStack> message(String name, String text) {
return Commands.literal(name)
.executes((CommandContext<CommandSourceStack> context) -> {
@@ -159,6 +176,52 @@ public final class ModdedStudioCommands {
return folder;
}
private static int open(CommandSourceStack source, String pack, long seed) {
File folder = resolvePack(source, pack);
if (folder == null) {
return 0;
}
IrisData data = IrisData.get(folder);
IrisDimension dimension = data.getDimensionLoader().load(folder.getName());
if (dimension == null) {
IrisModdedCommands.fail(source, "Pack '" + folder.getName() + "' has no dimensions/" + folder.getName() + ".json");
return 0;
}
IrisModdedCommands.ok(source, studioOpenComponent(dimension, folder, seed));
return 1;
}
private static MutableComponent studioOpenComponent(IrisDimension dimension, File folder, long seed) {
String dimensionKey = dimension.getLoadKey() == null ? folder.getName() : dimension.getLoadKey();
MutableComponent message = Component.empty();
message.append(ModdedCommandFeedback.header("Iris Studio"));
message.append(Component.literal("\n"));
message.append(ModdedCommandFeedback.text("Opening studio for the \"", ModdedCommandFeedback.DARK_GREEN));
message.append(ModdedCommandFeedback.text(dimension.getName(), ModdedCommandFeedback.PARAMETER_ALT));
message.append(ModdedCommandFeedback.text("\" pack", ModdedCommandFeedback.DARK_GREEN));
message.append(ModdedCommandFeedback.text(" (seed: " + seed + ")", ModdedCommandFeedback.VALUE));
message.append(Component.literal("\n"));
message.append(ModdedCommandFeedback.text("Pack ", ModdedCommandFeedback.DARK_GREEN));
message.append(ModdedCommandFeedback.text(dimensionKey, ModdedCommandFeedback.PARAMETER));
message.append(ModdedCommandFeedback.text(" is ready at ", ModdedCommandFeedback.DESCRIPTION));
message.append(ModdedCommandFeedback.text(folder.getAbsolutePath(), ModdedCommandFeedback.VALUE));
message.append(Component.literal("\n"));
message.append(ModdedCommandFeedback.text("Modded servers cannot create Bukkit's temporary studio world at runtime; edit this pack directly, then use the matching world/datapack workflow or ", ModdedCommandFeedback.DESCRIPTION));
message.append(ModdedCommandFeedback.button("/iris regen", "/iris regen", "Regenerate nearby chunks after editing this pack", false));
message.append(ModdedCommandFeedback.text(" in an Iris dimension.", ModdedCommandFeedback.DESCRIPTION));
message.append(Component.literal("\n"));
message.append(ModdedCommandFeedback.button("Validate", "/iris pack validate " + dimensionKey, "Validate this pack before loading it", true));
message.append(ModdedCommandFeedback.text(" ", ModdedCommandFeedback.OPTIONAL));
message.append(ModdedCommandFeedback.button("World Help", "/iris world", "Open Iris world command help", true));
message.append(ModdedCommandFeedback.text(" ", ModdedCommandFeedback.OPTIONAL));
message.append(ModdedCommandFeedback.button("Package", "/iris studio package " + dimensionKey, "Package this dimension", false));
message.append(Component.literal("\n"));
message.append(ModdedCommandFeedback.footer());
return message;
}
private static int version(CommandSourceStack source, String pack) {
File folder = resolvePack(source, pack);
if (folder == null) {
@@ -20,6 +20,7 @@ package art.arcane.iris.neoforge;
import art.arcane.iris.modded.IrisModdedChunkGenerator;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.modded.ModdedParityProbe;
import art.arcane.iris.modded.ModdedWorldCheck;
import art.arcane.iris.modded.ModdedWorldEngines;
@@ -42,20 +43,16 @@ import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import net.neoforged.neoforge.event.tick.ServerTickEvent;
import net.neoforged.neoforge.registries.DeferredRegister;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod("irisworldgen")
public final class IrisNeoForgeBootstrap {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
public IrisNeoForgeBootstrap(IEventBus modBus) {
ModdedEngineBootstrap.initialize(new NeoForgeModdedLoader());
String modVersion = ModList.get().getModContainerById("irisworldgen")
.map((ModContainer container) -> container.getModInfo().getVersion().toString())
.orElse("unknown");
VersionInfo versionInfo = FMLLoader.getCurrent().getVersionInfo();
LOGGER.info("Iris {} bootstrapping on Minecraft {} (NeoForge {})", modVersion, versionInfo.mcVersion(), versionInfo.neoForgeVersion());
ModdedIrisLog.info("Iris " + modVersion + " bootstrapping on Minecraft " + versionInfo.mcVersion() + " (NeoForge " + versionInfo.neoForgeVersion() + ")");
ModdedEngineBootstrap.selfTest(IrisNeoForgeBootstrap.class.getClassLoader());
ModdedEngineBootstrap.bind();
@@ -63,7 +60,7 @@ public final class IrisNeoForgeBootstrap {
DeferredRegister<MapCodec<? extends ChunkGenerator>> chunkGenerators = DeferredRegister.create(Registries.CHUNK_GENERATOR, "irisworldgen");
chunkGenerators.register("iris", () -> IrisModdedChunkGenerator.CODEC);
chunkGenerators.register(modBus);
LOGGER.info("Iris chunk generator registered as irisworldgen:iris");
ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris");
NeoForge.EVENT_BUS.addListener((ServerStoppingEvent event) -> {
ModdedObjectUndo.clearAll();
@@ -86,13 +83,13 @@ public final class IrisNeoForgeBootstrap {
String parity = System.getProperty("iris.parity");
if (parity != null) {
LOGGER.info("Iris parity probe armed: {}", parity);
ModdedIrisLog.info("Iris parity probe armed: " + parity);
ModdedParityProbe.schedule(parity);
}
String worldCheck = System.getProperty("iris.worldcheck");
if (worldCheck != null) {
LOGGER.info("Iris world check armed");
ModdedIrisLog.info("Iris world check armed");
ModdedWorldCheck.schedule();
}
}
+90 -8
View File
@@ -70,6 +70,9 @@ String fabricArtifactName = irisArtifactName('Fabric', "${minecraftVersion}+${lo
String forgeArtifactName = irisArtifactName('Forge', "${minecraftVersion}+${loaderDisplayVersion(forgeVersion)}")
String neoForgeArtifactName = irisArtifactName('NeoForge', "${minecraftVersion}+${loaderDisplayVersion(neoForgeVersion)}")
extensions.extraProperties.set('bukkitArtifactName', bukkitArtifactName)
extensions.extraProperties.set('fabricArtifactName', fabricArtifactName)
extensions.extraProperties.set('forgeArtifactName', forgeArtifactName)
extensions.extraProperties.set('neoForgeArtifactName', neoForgeArtifactName)
apply plugin: ApiGenerator
@@ -86,7 +89,13 @@ registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - D
registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins')
// ========================== UNIX ==============================
registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins')
registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins')
registerCustomOutputTaskUnix(
'PsychoLT',
'/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins',
'/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/fabric-mod-consumers/dropins/mods',
'/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/forge-mod-consumers/dropins/mods',
'/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/neoforge-mod-consumers/dropins/mods'
)
registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
// ==============================================================
@@ -232,6 +241,14 @@ tasks.register('buildNeoforge', Copy) {
}
}
tasks.named('forgeJar').configure {
mustRunAfter(tasks.named('fabricJar'))
}
tasks.named('neoforgeJar').configure {
mustRunAfter(tasks.named('forgeJar'))
}
tasks.register('buildAll') {
group = 'iris'
dependsOn('buildBukkit', 'buildFabric', 'buildForge', 'buildNeoforge', ':spi:jar')
@@ -375,14 +392,15 @@ void registerCustomOutputTask(String name, String path) {
return
}
tasks.register("build${name}", Copy) {
group = 'development'
outputs.upToDateWhen { false }
dependsOn('iris')
from(layout.buildDirectory.file((String) project.ext.bukkitArtifactName))
into(file(path))
rename { String ignored -> 'Iris.jar' }
createCustomOutputTasks(name, path)
}
void registerCustomOutputTask(String name, String path, String fabricPath, String forgePath, String neoForgePath) {
if (!System.getProperty('os.name').toLowerCase().contains('windows')) {
return
}
createCustomOutputTasks(name, path, fabricPath, forgePath, neoForgePath)
}
void registerCustomOutputTaskUnix(String name, String path) {
@@ -390,6 +408,44 @@ void registerCustomOutputTaskUnix(String name, String path) {
return
}
createCustomOutputTasks(name, path)
}
void registerCustomOutputTaskUnix(String name, String path, String fabricPath, String forgePath, String neoForgePath) {
if (System.getProperty('os.name').toLowerCase().contains('windows')) {
return
}
createCustomOutputTasks(name, path, fabricPath, forgePath, neoForgePath)
}
void createCustomOutputTasks(String name, String path) {
createBukkitOutputTask(name, path)
tasks.register("buildAll${name}") {
group = 'development'
dependsOn("build${name}")
}
}
void createCustomOutputTasks(String name, String path, String fabricPath, String forgePath, String neoForgePath) {
createBukkitOutputTask(name, path)
createPlatformOutputTask(name, fabricPath, 'Fabric', 'buildFabric', "adapters/fabric/build/libs/${String.valueOf(project.ext.fabricArtifactName)}")
createPlatformOutputTask(name, forgePath, 'Forge', 'buildForge', "adapters/forge/build/libs/${String.valueOf(project.ext.forgeArtifactName)}")
createPlatformOutputTask(name, neoForgePath, 'Neoforge', 'buildNeoforge', "adapters/neoforge/build/libs/${String.valueOf(project.ext.neoForgeArtifactName)}")
tasks.register("buildAll${name}") {
group = 'development'
dependsOn(
"build${name}",
"build${name}Fabric",
"build${name}Forge",
"build${name}Neoforge"
)
}
}
void createBukkitOutputTask(String name, String path) {
tasks.register("build${name}", Copy) {
group = 'development'
outputs.upToDateWhen { false }
@@ -397,5 +453,31 @@ void registerCustomOutputTaskUnix(String name, String path) {
from(layout.buildDirectory.file((String) project.ext.bukkitArtifactName))
into(file(path))
rename { String ignored -> 'Iris.jar' }
doFirst {
delete(
file("${path}/Iris-Fabric.jar"),
file("${path}/Iris-Forge.jar"),
file("${path}/Iris-Neoforge.jar")
)
}
}
}
void createPlatformOutputTask(String name, String path, String platform, String buildTask, String sourcePath) {
tasks.register("build${name}${platform}", Copy) {
group = 'development'
outputs.upToDateWhen { false }
dependsOn(buildTask)
from(layout.projectDirectory.file(sourcePath))
into(file(path))
rename { String ignored -> "Iris-${platform}.jar" }
doFirst {
delete(
file("${path}/Iris.jar"),
file("${path}/Iris-Fabric.jar"),
file("${path}/Iris-Forge.jar"),
file("${path}/Iris-Neoforge.jar")
)
}
}
}
@@ -2,6 +2,7 @@ package art.arcane.iris.core.safeguard;
import art.arcane.iris.BuildConstants;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.splash.IrisSplashRenderer;
import art.arcane.iris.platform.bukkit.BukkitPlatform;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.util.common.format.C;
@@ -11,21 +12,12 @@ import org.bukkit.Bukkit;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
public enum Mode {
STABLE(C.IRIS, C.AQUA),
WARNING(C.GOLD, C.YELLOW),
UNSTABLE(C.RED, C.GOLD);
private static final int SPLASH_WIDTH = 53;
private static final int SPLASH_HEIGHT = 11;
private static final double SPLASH_CENTER_X = 26.0;
private static final double SPLASH_CENTER_Y = 5.0;
private static final double SPLASH_ASPECT = 2.15;
private static final double SPLASH_EYE_A = 5.6;
private static final double SPLASH_EYE_B = 4.6;
private final C color;
private final C glow;
private final String id;
@@ -70,7 +62,7 @@ public enum Mode {
String startupDate = getStartupDate();
int javaVersion = getJavaVersion();
String[] splash = renderSplashLogo();
String[] splash = IrisSplashRenderer.render(this::splashTone);
String[] info = new String[]{
"",
@@ -94,38 +86,6 @@ public enum Mode {
IrisLogging.info(builder.toString());
}
private String[] renderSplashLogo() {
ThreadLocalRandom random = ThreadLocalRandom.current();
double headThickness = 3.7 + random.nextDouble() * 0.6;
double tipReach = 19.0 + random.nextDouble() * 4.0;
double notchX = 3.5 + random.nextDouble() * 1.5;
double[][][] spines = splashFinSpines(tipReach);
double[][][] voids = splashNotchVoids(notchX);
String[] lines = new String[SPLASH_HEIGHT];
for (int y = 0; y < SPLASH_HEIGHT; y++) {
StringBuilder line = new StringBuilder();
String activeTone = null;
for (int x = 0; x < SPLASH_WIDTH; x++) {
char glyph = splashEyeGlyph(x, y);
if (glyph == 0) {
glyph = splashFinGlyph(x, y, spines, voids, headThickness);
}
if (glyph == 0 || glyph == ' ') {
line.append(' ');
continue;
}
String tone = splashTone(glyph);
if (!tone.equals(activeTone)) {
line.append(tone);
activeTone = tone;
}
line.append(glyph);
}
lines[y] = line.toString();
}
return lines;
}
private String splashTone(char glyph) {
return switch (glyph) {
case '=' -> C.GRAY.toString();
@@ -136,129 +96,6 @@ public enum Mode {
};
}
private char splashEyeGlyph(int x, int y) {
if (y == 5 && Math.abs(x - 26) <= 1) {
if (x == 25) {
return '(';
}
if (x == 27) {
return ')';
}
return ' ';
}
double radius = splashEyeRadius(x, y);
if (radius <= 0.50) {
return '@';
}
if (radius <= 0.72) {
return '%';
}
if (radius <= 0.88) {
return '*';
}
return 0;
}
private char splashFinGlyph(int x, int y, double[][][] spines, double[][][] voids, double headThickness) {
if (splashEyeRadius(x, y) <= 1.26) {
return 0;
}
double voidDistance = Double.MAX_VALUE;
for (double[][] notch : voids) {
for (double[] sample : notch) {
double voidRadius = Math.max(0.25, 0.95 - 0.65 * sample[2]);
double dx = (x - sample[0]) / SPLASH_ASPECT;
double dy = y - sample[1];
double distance = Math.sqrt(dx * dx + dy * dy) / voidRadius;
if (distance < voidDistance) {
voidDistance = distance;
}
}
}
if (voidDistance < 1.0) {
return 0;
}
double best = Double.MAX_VALUE;
double bestT = 0.0;
for (double[][] spine : spines) {
for (int i = 0; i < spine.length; i++) {
double t = i / (double) (spine.length - 1);
double thickness = headThickness * Math.pow(1.0 - t, 1.3) + 0.6;
double dx = (x - spine[i][0]) / SPLASH_ASPECT;
double dy = y - spine[i][1];
double distance = Math.sqrt(dx * dx + dy * dy) / thickness;
if (distance < best) {
best = distance;
bestT = t;
}
}
}
double shade = best
+ Math.max(0.0, 3.0 - Math.min(x, SPLASH_WIDTH - 1 - x)) * 0.12
+ Math.max(0.0, 0.10 - bestT) * 1.5
+ Math.max(0.0, 1.30 - voidDistance) * 0.15;
if (shade > 1.0) {
return 0;
}
int level = shade <= 0.70 ? 0 : shade <= 0.90 ? 1 : 2;
if (bestT > 0.97) {
level += 2;
} else if (bestT > 0.88) {
level += 1;
}
return switch (Math.min(level, 2)) {
case 0 -> '#';
case 1 -> '=';
default -> '-';
};
}
private double splashEyeRadius(int x, int y) {
double dx = (x - SPLASH_CENTER_X) / SPLASH_ASPECT;
double dy = y - SPLASH_CENTER_Y;
double nx = dx / SPLASH_EYE_A;
double ny = dy / SPLASH_EYE_B;
return Math.sqrt(nx * nx + ny * ny);
}
private double[][][] splashNotchVoids(double notchX) {
double[][] control = {{notchX + 0.8, 12.0}, {notchX, 9.6}, {notchX - 1.4, 7.8}};
int samples = 31;
double[][] left = new double[samples][3];
double[][] right = new double[samples][3];
for (int i = 0; i < samples; i++) {
double t = i / (double) (samples - 1);
double u = 1.0 - t;
double bx = u * u * control[0][0] + 2.0 * u * t * control[1][0] + t * t * control[2][0];
double by = u * u * control[0][1] + 2.0 * u * t * control[1][1] + t * t * control[2][1];
left[i][0] = bx;
left[i][1] = by;
left[i][2] = t;
right[i][0] = (SPLASH_WIDTH - 1.0) - bx;
right[i][1] = (SPLASH_HEIGHT - 1.0) - by;
right[i][2] = t;
}
return new double[][][]{left, right};
}
private double[][][] splashFinSpines(double tipReach) {
double[][] control = {{-1.5, 9.8}, {0.4, 4.4}, {5.2, 1.0}, {tipReach, 0.4}};
int samples = 61;
double[][] left = new double[samples][2];
double[][] right = new double[samples][2];
for (int i = 0; i < samples; i++) {
double t = i / (double) (samples - 1);
double u = 1.0 - t;
double bx = u * u * u * control[0][0] + 3.0 * u * u * t * control[1][0] + 3.0 * u * t * t * control[2][0] + t * t * t * control[3][0];
double by = u * u * u * control[0][1] + 3.0 * u * u * t * control[1][1] + 3.0 * u * t * t * control[2][1] + t * t * t * control[3][1];
left[i][0] = bx;
left[i][1] = by;
right[i][0] = (SPLASH_WIDTH - 1.0) - bx;
right[i][1] = (SPLASH_HEIGHT - 1.0) - by;
}
return new double[][][]{left, right};
}
private String wrap(String tag) {
return C.BOLD.toString() + C.DARK_GRAY + "[" + C.BOLD + color + tag + C.BOLD + C.DARK_GRAY + "]" + C.RESET;
}
@@ -0,0 +1,78 @@
package art.arcane.iris.core.splash;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public final class IrisSplashPackScanner {
private IrisSplashPackScanner() {
}
public static List<SplashPackMetadata> collect(File packFolder, SplashPackErrorReporter reporter) {
if (packFolder == null || !packFolder.isDirectory()) {
return List.of();
}
File[] folders = packFolder.listFiles(File::isDirectory);
if (folders == null || folders.length == 0) {
return List.of();
}
List<SplashPackMetadata> packs = new ArrayList<>(folders.length);
for (File folder : folders) {
SplashPackMetadata metadata = read(folder, reporter);
if (metadata != null) {
packs.add(metadata);
}
}
packs.sort(Comparator.comparing(SplashPackMetadata::name));
return packs;
}
public static SplashPackMetadata read(File pack, SplashPackErrorReporter reporter) {
if (pack == null || !pack.isDirectory()) {
return null;
}
String dimName = pack.getName();
File dimensionFile = new File(pack, "dimensions/" + dimName + ".json");
if (!dimensionFile.isFile()) {
return null;
}
try (FileReader reader = new FileReader(dimensionFile)) {
JsonObject json = JsonParser.parseReader(reader).getAsJsonObject();
if (!json.has("version")) {
return null;
}
return new SplashPackMetadata(dimName, json.get("version").getAsString());
} catch (IOException | JsonParseException | IllegalStateException error) {
report(reporter, "Failed to read splash metadata for dimension pack \"" + dimName + "\".", error);
return null;
}
}
private static void report(SplashPackErrorReporter reporter, String message, Throwable error) {
if (reporter == null) {
return;
}
reporter.report(message, error);
}
public record SplashPackMetadata(String name, String version) {
}
public interface SplashPackErrorReporter {
void report(String message, Throwable error);
}
}
@@ -0,0 +1,181 @@
package art.arcane.iris.core.splash;
import java.util.concurrent.ThreadLocalRandom;
public final class IrisSplashRenderer {
private static final int SPLASH_WIDTH = 53;
private static final int SPLASH_HEIGHT = 11;
private static final double SPLASH_CENTER_X = 26.0;
private static final double SPLASH_CENTER_Y = 5.0;
private static final double SPLASH_ASPECT = 2.15;
private static final double SPLASH_EYE_A = 5.6;
private static final double SPLASH_EYE_B = 4.6;
private static final SplashTone PLAIN_TONE = (glyph) -> "";
private IrisSplashRenderer() {
}
public static String[] renderPlain() {
return render(PLAIN_TONE);
}
public static String[] render(SplashTone tone) {
SplashTone splashTone = tone == null ? PLAIN_TONE : tone;
ThreadLocalRandom random = ThreadLocalRandom.current();
double headThickness = 3.7 + random.nextDouble() * 0.6;
double tipReach = 19.0 + random.nextDouble() * 4.0;
double notchX = 3.5 + random.nextDouble() * 1.5;
double[][][] spines = splashFinSpines(tipReach);
double[][][] voids = splashNotchVoids(notchX);
String[] lines = new String[SPLASH_HEIGHT];
for (int y = 0; y < SPLASH_HEIGHT; y++) {
StringBuilder line = new StringBuilder();
String activeTone = null;
for (int x = 0; x < SPLASH_WIDTH; x++) {
char glyph = splashEyeGlyph(x, y);
if (glyph == 0) {
glyph = splashFinGlyph(x, y, spines, voids, headThickness);
}
if (glyph == 0 || glyph == ' ') {
line.append(' ');
continue;
}
String glyphTone = splashTone.tone(glyph);
if (!glyphTone.isEmpty() && !glyphTone.equals(activeTone)) {
line.append(glyphTone);
activeTone = glyphTone;
}
line.append(glyph);
}
lines[y] = line.toString();
}
return lines;
}
private static char splashEyeGlyph(int x, int y) {
if (y == 5 && Math.abs(x - 26) <= 1) {
if (x == 25) {
return '(';
}
if (x == 27) {
return ')';
}
return ' ';
}
double radius = splashEyeRadius(x, y);
if (radius <= 0.50) {
return '@';
}
if (radius <= 0.72) {
return '%';
}
if (radius <= 0.88) {
return '*';
}
return 0;
}
private static char splashFinGlyph(int x, int y, double[][][] spines, double[][][] voids, double headThickness) {
if (splashEyeRadius(x, y) <= 1.26) {
return 0;
}
double voidDistance = Double.MAX_VALUE;
for (double[][] notch : voids) {
for (double[] sample : notch) {
double voidRadius = Math.max(0.25, 0.95 - 0.65 * sample[2]);
double dx = (x - sample[0]) / SPLASH_ASPECT;
double dy = y - sample[1];
double distance = Math.sqrt(dx * dx + dy * dy) / voidRadius;
if (distance < voidDistance) {
voidDistance = distance;
}
}
}
if (voidDistance < 1.0) {
return 0;
}
double best = Double.MAX_VALUE;
double bestT = 0.0;
for (double[][] spine : spines) {
for (int i = 0; i < spine.length; i++) {
double t = i / (double) (spine.length - 1);
double thickness = headThickness * Math.pow(1.0 - t, 1.3) + 0.6;
double dx = (x - spine[i][0]) / SPLASH_ASPECT;
double dy = y - spine[i][1];
double distance = Math.sqrt(dx * dx + dy * dy) / thickness;
if (distance < best) {
best = distance;
bestT = t;
}
}
}
double shade = best
+ Math.max(0.0, 3.0 - Math.min(x, SPLASH_WIDTH - 1 - x)) * 0.12
+ Math.max(0.0, 0.10 - bestT) * 1.5
+ Math.max(0.0, 1.30 - voidDistance) * 0.15;
if (shade > 1.0) {
return 0;
}
int level = shade <= 0.70 ? 0 : shade <= 0.90 ? 1 : 2;
if (bestT > 0.97) {
level += 2;
} else if (bestT > 0.88) {
level += 1;
}
return switch (Math.min(level, 2)) {
case 0 -> '#';
case 1 -> '=';
default -> '-';
};
}
private static double splashEyeRadius(int x, int y) {
double dx = (x - SPLASH_CENTER_X) / SPLASH_ASPECT;
double dy = y - SPLASH_CENTER_Y;
double nx = dx / SPLASH_EYE_A;
double ny = dy / SPLASH_EYE_B;
return Math.sqrt(nx * nx + ny * ny);
}
private static double[][][] splashNotchVoids(double notchX) {
double[][] control = {{notchX + 0.8, 12.0}, {notchX, 9.6}, {notchX - 1.4, 7.8}};
int samples = 31;
double[][] left = new double[samples][3];
double[][] right = new double[samples][3];
for (int i = 0; i < samples; i++) {
double t = i / (double) (samples - 1);
double u = 1.0 - t;
double bx = u * u * control[0][0] + 2.0 * u * t * control[1][0] + t * t * control[2][0];
double by = u * u * control[0][1] + 2.0 * u * t * control[1][1] + t * t * control[2][1];
left[i][0] = bx;
left[i][1] = by;
left[i][2] = t;
right[i][0] = (SPLASH_WIDTH - 1.0) - bx;
right[i][1] = (SPLASH_HEIGHT - 1.0) - by;
right[i][2] = t;
}
return new double[][][]{left, right};
}
private static double[][][] splashFinSpines(double tipReach) {
double[][] control = {{-1.5, 9.8}, {0.4, 4.4}, {5.2, 1.0}, {tipReach, 0.4}};
int samples = 61;
double[][] left = new double[samples][2];
double[][] right = new double[samples][2];
for (int i = 0; i < samples; i++) {
double t = i / (double) (samples - 1);
double u = 1.0 - t;
double bx = u * u * u * control[0][0] + 3.0 * u * u * t * control[1][0] + 3.0 * u * t * t * control[2][0] + t * t * t * control[3][0];
double by = u * u * u * control[0][1] + 3.0 * u * u * t * control[1][1] + 3.0 * u * t * t * control[2][1] + t * t * t * control[3][1];
left[i][0] = bx;
left[i][1] = by;
right[i][0] = (SPLASH_WIDTH - 1.0) - bx;
right[i][1] = (SPLASH_HEIGHT - 1.0) - by;
}
return new double[][][]{left, right};
}
public interface SplashTone {
String tone(char glyph);
}
}
@@ -19,13 +19,17 @@
package art.arcane.iris.spi;
import java.util.IllegalFormatException;
import java.util.regex.Pattern;
public final class IrisLogging {
private static final Pattern LEGACY_COLOR = Pattern.compile("(?i)\\u00a7[0-9A-FK-ORX]");
private static final Pattern MINI_MESSAGE_TAG = Pattern.compile("(?i)</?(?:reset|bold|b|italic|i|underlined?|u|strikethrough|st|obfuscated?|obf|black|dark_blue|dark_green|dark_aqua|dark_red|dark_purple|gold|gray|dark_gray|blue|green|aqua|red|light_purple|yellow|white|gradient|font|hover|click|rainbow)(?::[^>\\n]{0,96})?>|<#[0-9a-f]{6}>");
private IrisLogging() {
}
public static void info(String format, Object... args) {
emit(LogLevel.INFO, safeFormat(format, args));
emit(LogLevel.INFO, format(format, args));
}
public static void debug(String message) {
@@ -33,11 +37,11 @@ public final class IrisLogging {
}
public static void warn(String format, Object... args) {
emit(LogLevel.WARN, safeFormat(format, args));
emit(LogLevel.WARN, format(format, args));
}
public static void error(String format, Object... args) {
emit(LogLevel.ERROR, safeFormat(format, args));
emit(LogLevel.ERROR, format(format, args));
}
public static void msg(String message) {
@@ -46,7 +50,7 @@ public final class IrisLogging {
return;
}
System.out.println("[Iris] " + message);
System.out.println("[Iris] " + clean(message));
}
public static void reportError(String context, Throwable error) {
@@ -76,21 +80,7 @@ public final class IrisLogging {
}
}
private static void emit(LogLevel level, String message) {
if (IrisPlatforms.isBound()) {
IrisPlatforms.get().log(level, message);
return;
}
if (level == LogLevel.WARN || level == LogLevel.ERROR) {
System.err.println("[Iris/" + level + "] " + message);
return;
}
System.out.println("[Iris/" + level + "] " + message);
}
private static String safeFormat(String format, Object... args) {
public static String format(String format, Object... args) {
if (format == null) {
return "null";
}
@@ -105,4 +95,29 @@ public final class IrisLogging {
return format;
}
}
public static String clean(String message) {
if (message == null) {
return "null";
}
String legacyStripped = LEGACY_COLOR.matcher(message).replaceAll("");
return MINI_MESSAGE_TAG.matcher(legacyStripped).replaceAll("");
}
private static void emit(LogLevel level, String message) {
LogLevel target = level == null ? LogLevel.INFO : level;
if (IrisPlatforms.isBound()) {
IrisPlatforms.get().log(target, message);
return;
}
String output = clean(message);
if (target == LogLevel.WARN || target == LogLevel.ERROR) {
System.err.println("[Iris/" + target + "] " + output);
return;
}
System.out.println("[Iris/" + target + "] " + output);
}
}