mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-23 23:31:00 +00:00
d
d
This commit is contained in:
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -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);
|
||||
}
|
||||
|
||||
+84
@@ -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)) {
|
||||
|
||||
+182
-33
@@ -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) {
|
||||
|
||||
+58
-9
@@ -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
|
||||
|
||||
+69
-6
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user