mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 04:37:47 +00:00
Imagemap sanitization
This commit is contained in:
@@ -102,6 +102,9 @@ import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.io.InstanceState;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.plugin.ComponentLog;
|
||||
import art.arcane.volmlib.util.plugin.ComponentMessenger;
|
||||
import art.arcane.volmlib.util.plugin.ComponentText;
|
||||
import art.arcane.iris.util.common.misc.Bindings;
|
||||
import art.arcane.iris.util.common.misc.ServerProperties;
|
||||
import art.arcane.iris.util.common.misc.SlimJar;
|
||||
@@ -147,6 +150,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -161,7 +165,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
}
|
||||
|
||||
public static Iris instance;
|
||||
public static Bindings.Adventure audiences;
|
||||
public static MultiverseCoreLink linkMultiverseCore;
|
||||
public static IrisCompat compat;
|
||||
public static ChunkTickets tickets;
|
||||
@@ -256,7 +259,15 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
getSender().sendMessage(string);
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
instance.getLogger().info(instance.getTag() + IrisLogging.clean(string));
|
||||
Iris plugin = instance;
|
||||
String tag = plugin == null ? "" : plugin.getTag();
|
||||
ComponentLog.logMarkup(
|
||||
plugin,
|
||||
Logger.getLogger("Iris"),
|
||||
"[Iris] ",
|
||||
Level.INFO,
|
||||
tag + string,
|
||||
null);
|
||||
} catch (Throwable inner) {
|
||||
System.err.println("[Iris] Failed to emit log message: " + inner.getMessage());
|
||||
inner.printStackTrace(System.err);
|
||||
@@ -550,20 +561,13 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
|
||||
private static void diagnostic(Level level, String message) {
|
||||
String line = IrisLogging.clean(message);
|
||||
Iris plugin = instance;
|
||||
if (plugin != null) {
|
||||
try {
|
||||
plugin.getLogger().log(level, line);
|
||||
return;
|
||||
} catch (Throwable unavailable) {
|
||||
// Paper runs the bootstrap before a plugin logger exists; the streams below are all there is.
|
||||
}
|
||||
}
|
||||
if (level.intValue() >= Level.WARNING.intValue()) {
|
||||
System.err.println("[Iris] " + line);
|
||||
return;
|
||||
}
|
||||
System.out.println("[Iris] " + line);
|
||||
ComponentLog.log(
|
||||
instance,
|
||||
Logger.getLogger("Iris"),
|
||||
"[Iris] ",
|
||||
level,
|
||||
ComponentText.literal(line),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,7 +599,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
PaperLibBootstrap.install();
|
||||
SimdSupport.install();
|
||||
services = new KMap<>();
|
||||
setupAudience();
|
||||
BukkitPlatform.hostHud(new HudActionBar(this), new HudBossBarLane());
|
||||
Bindings.setupSentry();
|
||||
// Explicit, ordered service list: the previous reflective jar scan gave hash-ordered
|
||||
@@ -794,17 +797,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupAudience() {
|
||||
try {
|
||||
audiences = new Bindings.Adventure(this);
|
||||
BukkitPlatform.hostAudiences(audiences);
|
||||
} catch (Throwable e) {
|
||||
IrisSettings.get().getGeneral().setUseConsoleCustomColors(false);
|
||||
IrisSettings.get().getGeneral().setUseCustomColorsIngame(false);
|
||||
Iris.reportError("Failed to set up Adventure; custom colors are disabled.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onEnable() {
|
||||
IrisPlatforms.bind(new BukkitPlatform());
|
||||
IrisStartupValidation.begin();
|
||||
@@ -1215,7 +1207,9 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
||||
}
|
||||
|
||||
public void imsg(CommandSender s, String msg) {
|
||||
s.sendMessage(C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg);
|
||||
ComponentMessenger.sendSection(
|
||||
s,
|
||||
C.IRIS + "[" + C.DARK_GRAY + "Iris" + C.IRIS + "]" + C.GRAY + ": " + msg);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+1
-1
@@ -398,7 +398,7 @@ public class CommandObject implements DirectorExecutor {
|
||||
MessageArgument.trusted("value2", targets.size())
|
||||
));
|
||||
|
||||
org.bukkit.command.CommandSender s = sender();
|
||||
VolmitSender s = sender();
|
||||
List<TreePlausibilizeBatch.Target> queued = targets;
|
||||
J.a(() -> TreePlausibilizeBatch.run(queued, dryRun, reach, nearest, (TreePlausibilizeBatch.Output output) ->
|
||||
s.sendMessage(output.headline()
|
||||
|
||||
@@ -21,6 +21,7 @@ package art.arcane.iris.core.commands;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.gui.ImageMapStudioGUI;
|
||||
import art.arcane.iris.core.gui.NoiseExplorerGUI;
|
||||
import art.arcane.iris.core.gui.VisionGUI;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
@@ -263,6 +264,28 @@ public class CommandStudio implements DirectorExecutor {
|
||||
NoiseExplorerGUI.launchGeneratorKey(generatorKey, generator, seed);
|
||||
}
|
||||
|
||||
@Director(description = "Open the image-map studio (External GUI)", descriptionKey = "iris.director.commandstudio.director.open_image_map_studio_external_gui", aliases = {"imap"})
|
||||
public void imagemap(
|
||||
@Param(name = "world", description = "The Iris world whose active pack receives image-map exports", descriptionKey = "iris.director.commandstudio.param.world_open_image_map_studio", contextual = true, contextualOverride = true)
|
||||
World world
|
||||
) {
|
||||
if (noGUI()) {
|
||||
return;
|
||||
}
|
||||
if (!ImageMapStudioGUI.isAvailable()) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_YOU_MUST_HAVE_SERVER_LAUNCHED_GUIS_ENABLED_SETTINGS));
|
||||
return;
|
||||
}
|
||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_YOU_NEED_BE_SPECIFY_IRIS_GENERATED_WORLD));
|
||||
return;
|
||||
}
|
||||
|
||||
Engine activeEngine = IrisToolbelt.access(world).getEngine();
|
||||
ImageMapStudioGUI.launch(activeEngine);
|
||||
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_STUDIO_OPENING_IMAGE_MAP_STUDIO));
|
||||
}
|
||||
|
||||
@Director(description = "Show loot if a chest were right here", descriptionKey = "iris.director.commandstudio.director.show_loot_if_chest_were_right_here", origin = DirectorOrigin.PLAYER, sync = true)
|
||||
public void loot(
|
||||
@Param(description = "Fast insertion of items in virtual inventory (may cause performance drop)", descriptionKey = "iris.director.commandstudio.param.fast_insertion_items_virtual_inventory_may_cause_performance_drop", defaultValue = "false")
|
||||
|
||||
+71
@@ -36,11 +36,14 @@ import art.arcane.iris.engine.framework.NativeStructureVolume;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundary;
|
||||
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldBorder;
|
||||
|
||||
public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
|
||||
@Override
|
||||
@@ -80,6 +83,35 @@ public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
|
||||
"iris");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyWorldBoundary(Engine engine) {
|
||||
IrisDimension expectedDimension = engine.getDimension();
|
||||
IrisWorldBoundary configuredBoundary = expectedDimension.getWorldBoundary();
|
||||
if (configuredBoundary == null) {
|
||||
return;
|
||||
}
|
||||
IrisWorld expectedIrisWorld = engine.getWorld();
|
||||
World expectedWorld = BukkitWorldBinding.world(expectedIrisWorld);
|
||||
if (expectedWorld == null) {
|
||||
return;
|
||||
}
|
||||
IrisWorldBoundary boundary;
|
||||
try {
|
||||
boundary = IrisWorldBoundary.snapshot(configuredBoundary);
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.error("Invalid Iris world boundary for " + expectedWorld.getName() + ".");
|
||||
IrisLogging.reportError(error);
|
||||
throw propagateBoundaryFailure("Invalid Iris world boundary for '" + expectedWorld.getName() + "'.", error);
|
||||
}
|
||||
if (!J.runGlobal(() -> applyWorldBoundary(engine, expectedDimension, expectedIrisWorld, expectedWorld, boundary))) {
|
||||
IllegalStateException failure = new IllegalStateException(
|
||||
"Bukkit global scheduler rejected world-boundary application for '" + expectedWorld.getName() + "'.");
|
||||
IrisLogging.error(failure.getMessage());
|
||||
IrisLogging.reportError(failure);
|
||||
throw failure;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPregeneratorActive(Engine engine) {
|
||||
IrisWorld world = engine.getWorld();
|
||||
@@ -134,4 +166,43 @@ public final class BukkitEnginePlatformHooks implements EnginePlatformHooks {
|
||||
World world = BukkitWorldBinding.world(engine.getWorld());
|
||||
return world != null && IrisToolbelt.isWorldMaintenanceBypassingMantleStages(world);
|
||||
}
|
||||
|
||||
static void applyWorldBoundary(WorldBorder worldBorder, IrisWorldBoundary boundary) {
|
||||
if (boundary == null) {
|
||||
return;
|
||||
}
|
||||
worldBorder.setCenter(boundary.getCenter().getX(), boundary.getCenter().getZ());
|
||||
worldBorder.setSize(boundary.getSize());
|
||||
worldBorder.setWarningDistance(boundary.getWarningDistance());
|
||||
worldBorder.setDamageBuffer(boundary.getDamageBuffer());
|
||||
worldBorder.setDamageAmount(boundary.getDamageAmount());
|
||||
}
|
||||
|
||||
private static void applyWorldBoundary(Engine engine, IrisDimension expectedDimension,
|
||||
IrisWorld expectedIrisWorld, World expectedWorld,
|
||||
IrisWorldBoundary boundary) {
|
||||
if (engine.isClosed() || engine.isClosing() || engine.getDimension() != expectedDimension
|
||||
|| engine.getWorld() != expectedIrisWorld
|
||||
|| BukkitWorldBinding.world(expectedIrisWorld) != expectedWorld) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
applyWorldBoundary(expectedWorld.getWorldBorder(), boundary);
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.error("Failed to apply Iris world boundary to " + expectedWorld.getName() + ".");
|
||||
IrisLogging.reportError(error);
|
||||
throw propagateBoundaryFailure("Failed to apply Iris world boundary to '"
|
||||
+ expectedWorld.getName() + "'.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException propagateBoundaryFailure(String message, Throwable error) {
|
||||
if (error instanceof Error fatal) {
|
||||
throw fatal;
|
||||
}
|
||||
if (error instanceof RuntimeException runtimeException) {
|
||||
return runtimeException;
|
||||
}
|
||||
return new IllegalStateException(message, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ import java.util.Optional;
|
||||
import art.arcane.iris.core.localization.IrisLanguage;
|
||||
import art.arcane.iris.core.localization.IrisMessages;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
import art.arcane.volmlib.util.plugin.ComponentMessenger;
|
||||
public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, DirectorInvocationHook {
|
||||
private static final String ROOT_COMMAND = "iris";
|
||||
private static final String ROOT_PERMISSION = "iris.all";
|
||||
@@ -169,7 +170,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
||||
|
||||
public void executeRoot(CommandSender sender, String label, String[] args) {
|
||||
if (!sender.hasPermission(ROOT_PERMISSION)) {
|
||||
sender.sendMessage(IrisLanguage.text(
|
||||
ComponentMessenger.sendSection(sender, IrisLanguage.text(
|
||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||
MessageArgument.trusted("permission", ROOT_PERMISSION)
|
||||
));
|
||||
@@ -325,7 +326,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
||||
@Override
|
||||
public void sendMessage(String message) {
|
||||
if (message != null && !message.trim().isEmpty()) {
|
||||
sender.sendMessage(message);
|
||||
ComponentMessenger.sendLiteral(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -65,7 +65,9 @@ public class IrisDiagnosticLogLevelTest {
|
||||
|
||||
assertTrue(bridge, bridge.contains("diagnosticLevel(target)"));
|
||||
assertTrue(bridge, bridge.contains("diagnostic(diagnostic, message)"));
|
||||
assertTrue(source.contains("plugin.getLogger().log(level, line)"));
|
||||
String diagnostic = method(source, "private static void diagnostic(Level level, String message)");
|
||||
assertTrue(diagnostic, diagnostic.contains("ComponentLog.log("));
|
||||
assertTrue(diagnostic, diagnostic.contains("ComponentText.literal(line)"));
|
||||
}
|
||||
|
||||
private static String method(String source, String signature) {
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package art.arcane.iris.core.runtime;
|
||||
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundary;
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundaryCenter;
|
||||
import org.bukkit.WorldBorder;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class BukkitWorldBoundaryTest {
|
||||
@Test
|
||||
public void omittedBoundaryDoesNotTouchTheNativeWorld() {
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getDimension()).thenReturn(new IrisDimension());
|
||||
|
||||
new BukkitEnginePlatformHooks().applyWorldBoundary(engine);
|
||||
|
||||
verify(engine, never()).getWorld();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapsEveryBoundaryValueToBukkit() {
|
||||
WorldBorder worldBorder = mock(WorldBorder.class);
|
||||
IrisWorldBoundary boundary = new IrisWorldBoundary()
|
||||
.setCenter(new IrisWorldBoundaryCenter(12.5D, -7.25D))
|
||||
.setSize(4096D)
|
||||
.setWarningDistance(24)
|
||||
.setDamageBuffer(6.5D)
|
||||
.setDamageAmount(0.4D);
|
||||
|
||||
BukkitEnginePlatformHooks.applyWorldBoundary(worldBorder, boundary);
|
||||
|
||||
verify(worldBorder).setCenter(12.5D, -7.25D);
|
||||
verify(worldBorder).setSize(4096D);
|
||||
verify(worldBorder).setWarningDistance(24);
|
||||
verify(worldBorder).setDamageBuffer(6.5D);
|
||||
verify(worldBorder).setDamageAmount(0.4D);
|
||||
}
|
||||
}
|
||||
+1
@@ -184,6 +184,7 @@ public final class IrisModdedChunkGenerator extends ChunkGenerator {
|
||||
Engine replacement = ModdedWorldEngines.prepareReplacement(level, pack, packDimensionKey, seed);
|
||||
try {
|
||||
ModdedWorldEngines.installReplacement(level, replacement);
|
||||
replacement.getPlatformHooks().applyWorldBoundary(replacement);
|
||||
} catch (Throwable error) {
|
||||
try {
|
||||
ModdedWorldEngines.closeUnregistered(replacement);
|
||||
|
||||
+26
-1
@@ -50,7 +50,8 @@ public final class ModdedWorldEngines {
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
return ENGINES.computeIfAbsent(level, (ServerLevel l) -> create(l, pack, dimensionKey, seedOverride));
|
||||
return ENGINES.computeIfAbsent(level,
|
||||
(ServerLevel l) -> createAndApplyWorldBoundary(l, pack, dimensionKey, seedOverride));
|
||||
}
|
||||
|
||||
public static Collection<Engine> activeEngines() {
|
||||
@@ -98,9 +99,33 @@ public final class ModdedWorldEngines {
|
||||
|
||||
static void closeUnregistered(Engine engine) {
|
||||
close(engine);
|
||||
ENGINES.entrySet().removeIf((Map.Entry<ServerLevel, Engine> entry) -> entry.getValue() == engine);
|
||||
ModdedGuiHost.unbind(engine);
|
||||
}
|
||||
|
||||
private static Engine createAndApplyWorldBoundary(ServerLevel level, String pack,
|
||||
String dimensionKey, long seedOverride) {
|
||||
Engine engine = create(level, pack, dimensionKey, seedOverride);
|
||||
try {
|
||||
engine.getPlatformHooks().applyWorldBoundary(engine);
|
||||
return engine;
|
||||
} catch (Throwable failure) {
|
||||
try {
|
||||
close(engine);
|
||||
} catch (Throwable cleanupError) {
|
||||
failure.addSuppressed(cleanupError);
|
||||
}
|
||||
if (failure instanceof Error fatal) {
|
||||
throw fatal;
|
||||
}
|
||||
if (failure instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
throw new IllegalStateException("Failed to apply Iris world boundary for '"
|
||||
+ level.dimension().identifier() + "'.", failure);
|
||||
}
|
||||
}
|
||||
|
||||
private static Engine create(ServerLevel level, String pack, String dimensionKey, long seedOverride) {
|
||||
ModdedEngineBootstrap.bind();
|
||||
File packDir = resolvePack(pack, dimensionKey);
|
||||
|
||||
+50
-1
@@ -21,10 +21,14 @@ package art.arcane.iris.modded.command;
|
||||
import art.arcane.iris.modded.ModdedIrisLog;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.gui.GuiHost;
|
||||
import art.arcane.iris.core.gui.ImageMapStudioGUI;
|
||||
import art.arcane.iris.core.gui.NoiseExplorerGUI;
|
||||
import art.arcane.iris.core.gui.VisionGUI;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.pack.StructurePackageClosure;
|
||||
import art.arcane.iris.core.pack.ImageMapPackageClosure;
|
||||
import art.arcane.iris.core.pack.PackValidationResult;
|
||||
import art.arcane.iris.core.pack.PackValidator;
|
||||
import art.arcane.iris.core.project.IrisProjectCopier;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
@@ -151,6 +155,8 @@ public final class ModdedStudioCommands {
|
||||
root.then(noiseTree("nmap"));
|
||||
root.then(mapTree("map"));
|
||||
root.then(mapTree("render"));
|
||||
root.then(imageMapTree("imagemap"));
|
||||
root.then(imageMapTree("imap"));
|
||||
root.then(message("loot", "Loot simulation opens a Bukkit chest inventory GUI; it is not available on modded servers."));
|
||||
root.then(message("profile", "Pack performance profiling is part of the Bukkit studio toolchain and is not ported to modded servers."));
|
||||
root.then(message("spawn", "Iris entity spawning uses the Bukkit entity pipeline and is not ported to modded servers."));
|
||||
@@ -217,6 +223,11 @@ public final class ModdedStudioCommands {
|
||||
.executes((CommandContext<CommandSourceStack> context) -> map(context.getSource()));
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> imageMapTree(String name) {
|
||||
return Commands.literal(name)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> imageMap(context.getSource()));
|
||||
}
|
||||
|
||||
private static int noise(CommandSourceStack source, String generatorKey, long seed) {
|
||||
ServerLevel level = source.getLevel();
|
||||
Engine engine = IrisModdedCommands.engineFor(level);
|
||||
@@ -266,6 +277,29 @@ public final class ModdedStudioCommands {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int imageMap(CommandSourceStack source) {
|
||||
ServerLevel level = source.getLevel();
|
||||
Engine engine = IrisModdedCommands.engineFor(level);
|
||||
if (engine == null) {
|
||||
IrisModdedCommands.fail(source, IrisLanguage.plain(
|
||||
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_IMAGE_MAP_REQUIRES_IRIS_DIMENSION
|
||||
));
|
||||
return 0;
|
||||
}
|
||||
if (!ImageMapStudioGUI.isAvailable()) {
|
||||
IrisModdedCommands.fail(source, guiUnavailableMessage());
|
||||
return 0;
|
||||
}
|
||||
ServerPlayer player = source.getPlayer();
|
||||
ModdedGuiHost.bindContext(source.getServer(), level, engine, player == null ? null : player.getUUID());
|
||||
ImageMapStudioGUI.launch(engine);
|
||||
IrisModdedCommands.ok(source, IrisLanguage.plain(
|
||||
ModdedCommandMessages.MODDED_STUDIO_COMMANDS_OPENING_IMAGE_MAP_STUDIO_ON_SERVER_DISPLAY,
|
||||
MessageArgument.untrusted("value", level.dimension().identifier())
|
||||
));
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static LiteralArgumentBuilder<CommandSourceStack> workspaceTree(String name, boolean open) {
|
||||
return Commands.literal(name)
|
||||
.executes((CommandContext<CommandSourceStack> context) -> workspace(context.getSource(), null, open))
|
||||
@@ -640,6 +674,11 @@ public final class ModdedStudioCommands {
|
||||
}
|
||||
|
||||
private static File compilePackage(File packFolder, String dimKey) throws IOException {
|
||||
PackValidationResult validation = PackValidator.validateForPackaging(packFolder);
|
||||
if (!validation.isLoadable()) {
|
||||
throw new IOException("Pack validation failed before packaging: "
|
||||
+ String.join("; ", validation.getBlockingErrors()));
|
||||
}
|
||||
IrisData dm = IrisData.get(packFolder);
|
||||
IrisDimension dimension = dm.getDimensionLoader().load(dimKey);
|
||||
if (dimension == null) {
|
||||
@@ -660,7 +699,16 @@ public final class ModdedStudioCommands {
|
||||
LinkedHashSet<String> markerKeys = new LinkedHashSet<>();
|
||||
LinkedHashSet<String> structureKeys = new LinkedHashSet<>();
|
||||
|
||||
regionKeys.addAll(dimension.getRegions());
|
||||
dimension.getAllRegions(() -> dm).forEach((IrisRegion region) -> {
|
||||
if (region != null && region.getLoadKey() != null) {
|
||||
regionKeys.add(region.getLoadKey());
|
||||
}
|
||||
});
|
||||
dimension.getReachableBiomes(() -> dm).forEach((IrisBiome biome) -> {
|
||||
if (biome != null && biome.getLoadKey() != null) {
|
||||
biomeKeys.add(biome.getLoadKey());
|
||||
}
|
||||
});
|
||||
lootKeys.addAll(dimension.getLoot().getTables());
|
||||
spawnerKeys.addAll(dimension.getEntitySpawners());
|
||||
collectStructureKeys(structureKeys, dimension.getStructures());
|
||||
@@ -722,6 +770,7 @@ public final class ModdedStudioCommands {
|
||||
throw new IOException("Structure package closure is invalid: " + String.join("; ", structureClosure.errors()));
|
||||
}
|
||||
hashes.append(structureClosure.writeTo(folder, true));
|
||||
hashes.append(ImageMapPackageClosure.writeAll(dm, folder, true));
|
||||
for (String objectKey : objectKeys) {
|
||||
try {
|
||||
File objectFile = dm.getObjectLoader().findFile(objectKey);
|
||||
|
||||
+78
@@ -28,6 +28,7 @@ import art.arcane.iris.engine.framework.NativeStructureVolume;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisDimensionRuntimeContract;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundary;
|
||||
import art.arcane.iris.modded.IrisModdedChunkGenerator;
|
||||
import art.arcane.iris.modded.ModdedDimensionManager;
|
||||
import art.arcane.iris.modded.ModdedForcedDatapack;
|
||||
@@ -38,6 +39,8 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.io.ReactiveFolder;
|
||||
import art.arcane.volmlib.util.scheduling.ChronoLatch;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
@@ -120,6 +123,42 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
|
||||
"irisworldgen");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyWorldBoundary(Engine engine) {
|
||||
IrisDimension expectedDimension = engine.getDimension();
|
||||
IrisWorldBoundary configuredBoundary = expectedDimension.getWorldBoundary();
|
||||
if (configuredBoundary == null) {
|
||||
return;
|
||||
}
|
||||
IrisWorld expectedIrisWorld = engine.getWorld();
|
||||
if (expectedIrisWorld == null || !expectedIrisWorld.hasPlatformWorld()
|
||||
|| !(expectedIrisWorld.platformWorld().nativeHandle() instanceof ServerLevel expectedLevel)) {
|
||||
return;
|
||||
}
|
||||
IrisWorldBoundary boundary;
|
||||
try {
|
||||
boundary = IrisWorldBoundary.snapshot(configuredBoundary);
|
||||
} catch (Throwable error) {
|
||||
ModdedIrisLog.error("Invalid Iris world boundary for "
|
||||
+ expectedLevel.dimension().identifier() + ".", error);
|
||||
throw propagateBoundaryFailure("Invalid Iris world boundary for '"
|
||||
+ expectedLevel.dimension().identifier() + "'.", error);
|
||||
}
|
||||
MinecraftServer server = expectedLevel.getServer();
|
||||
if (server.isSameThread()) {
|
||||
applyWorldBoundary(engine, expectedDimension, expectedIrisWorld, expectedLevel, boundary);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
server.execute(() -> applyWorldBoundary(engine, expectedDimension, expectedIrisWorld, expectedLevel, boundary));
|
||||
} catch (Throwable error) {
|
||||
ModdedIrisLog.error("Failed to schedule Iris world-boundary application for "
|
||||
+ expectedLevel.dimension().identifier() + ".", error);
|
||||
throw propagateBoundaryFailure("Failed to schedule Iris world-boundary application for '"
|
||||
+ expectedLevel.dimension().identifier() + "'.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServerTick(MinecraftServer server) {
|
||||
ExecutorService active = executor;
|
||||
@@ -268,6 +307,45 @@ public final class ModdedStudioHotloadService implements ModdedTickableService,
|
||||
return false;
|
||||
}
|
||||
|
||||
static void applyWorldBoundary(WorldBorder worldBorder, IrisWorldBoundary boundary) {
|
||||
if (boundary == null) {
|
||||
return;
|
||||
}
|
||||
worldBorder.setCenter(boundary.getCenter().getX(), boundary.getCenter().getZ());
|
||||
worldBorder.setSize(boundary.getSize());
|
||||
worldBorder.setWarningBlocks(boundary.getWarningDistance());
|
||||
worldBorder.setSafeZone(boundary.getDamageBuffer());
|
||||
worldBorder.setDamagePerBlock(boundary.getDamageAmount());
|
||||
}
|
||||
|
||||
private static void applyWorldBoundary(Engine engine, IrisDimension expectedDimension,
|
||||
IrisWorld expectedIrisWorld, ServerLevel expectedLevel,
|
||||
IrisWorldBoundary boundary) {
|
||||
if (engine.isClosed() || engine.isClosing() || engine.getDimension() != expectedDimension
|
||||
|| engine.getWorld() != expectedIrisWorld
|
||||
|| expectedIrisWorld.platformWorld().nativeHandle() != expectedLevel) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
applyWorldBoundary(expectedLevel.getWorldBorder(), boundary);
|
||||
} catch (Throwable error) {
|
||||
ModdedIrisLog.error("Failed to apply Iris world boundary to "
|
||||
+ expectedLevel.dimension().identifier() + ".", error);
|
||||
throw propagateBoundaryFailure("Failed to apply Iris world boundary to '"
|
||||
+ expectedLevel.dimension().identifier() + "'.", error);
|
||||
}
|
||||
}
|
||||
|
||||
private static RuntimeException propagateBoundaryFailure(String message, Throwable error) {
|
||||
if (error instanceof Error fatal) {
|
||||
throw fatal;
|
||||
}
|
||||
if (error instanceof RuntimeException runtimeException) {
|
||||
return runtimeException;
|
||||
}
|
||||
return new IllegalStateException(message, error);
|
||||
}
|
||||
|
||||
private void writeWorkspace(Engine engine, String operation) {
|
||||
try {
|
||||
IrisData data = engine.getData();
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package art.arcane.iris.modded.service;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundary;
|
||||
import art.arcane.iris.engine.object.IrisWorldBoundaryCenter;
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ModdedWorldBoundaryTest {
|
||||
@Test
|
||||
public void omittedBoundaryDoesNotTouchTheNativeLevel() {
|
||||
WorldBorder worldBorder = new WorldBorder();
|
||||
worldBorder.setCenter(3.5D, -4.5D);
|
||||
worldBorder.setSize(1024D);
|
||||
worldBorder.setWarningBlocks(9);
|
||||
worldBorder.setSafeZone(2.5D);
|
||||
worldBorder.setDamagePerBlock(0.75D);
|
||||
|
||||
ModdedStudioHotloadService.applyWorldBoundary(worldBorder, null);
|
||||
|
||||
assertEquals(3.5D, worldBorder.getCenterX(), 0D);
|
||||
assertEquals(-4.5D, worldBorder.getCenterZ(), 0D);
|
||||
assertEquals(1024D, worldBorder.getSize(), 0D);
|
||||
assertEquals(9, worldBorder.getWarningBlocks());
|
||||
assertEquals(2.5D, worldBorder.getSafeZone(), 0D);
|
||||
assertEquals(0.75D, worldBorder.getDamagePerBlock(), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapsEveryBoundaryValueToMinecraft() {
|
||||
WorldBorder worldBorder = new WorldBorder();
|
||||
IrisWorldBoundary boundary = new IrisWorldBoundary()
|
||||
.setCenter(new IrisWorldBoundaryCenter(12.5D, -7.25D))
|
||||
.setSize(4096D)
|
||||
.setWarningDistance(24)
|
||||
.setDamageBuffer(6.5D)
|
||||
.setDamageAmount(0.4D);
|
||||
|
||||
ModdedStudioHotloadService.applyWorldBoundary(worldBorder, boundary);
|
||||
|
||||
assertEquals(12.5D, worldBorder.getCenterX(), 0D);
|
||||
assertEquals(-7.25D, worldBorder.getCenterZ(), 0D);
|
||||
assertEquals(4096D, worldBorder.getSize(), 0D);
|
||||
assertEquals(24, worldBorder.getWarningBlocks());
|
||||
assertEquals(6.5D, worldBorder.getSafeZone(), 0D);
|
||||
assertEquals(0.4D, worldBorder.getDamagePerBlock(), 0D);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user