mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
ddd
This commit is contained in:
79 files changed
+425
-1675
No files matched your search
@@ -117,7 +117,9 @@ public final class IrisWorldStorage {
|
||||
throw new IllegalArgumentException("World identifier is invalid: " + requestedName);
|
||||
}
|
||||
} else {
|
||||
key = keyFromName(requestedName, levelName);
|
||||
// "iris worlds" and Multiverse print the Bukkit startup name (<level>_iris_<key>), so
|
||||
// accept that form here instead of turning it into iris:<level>_iris_<key>.
|
||||
key = keyFromConfiguredWorldName(requestedName, levelName);
|
||||
}
|
||||
|
||||
if (!IRIS_NAMESPACE.equals(key.getNamespace()) || !key.getKey().matches("[a-z0-9_-]+")) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.WorldCreator;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* WorldCreator.ofKey and WorldCreator#key are Paper-API-only. Once a call throws
|
||||
@@ -12,31 +15,40 @@ import java.io.File;
|
||||
* straight to the fallback. The fallback derives names/keys through IrisWorldStorage's
|
||||
* current configured-name mapping so persistent Spigot worlds round-trip without changing
|
||||
* their startup directory.
|
||||
*
|
||||
* <p>Paper names a keyed creator {@code <namespace>_<key>} and refuses a creator built from a name
|
||||
* and a key together, but it names that same world {@code <level>_<namespace>_<key>} when it loads
|
||||
* it out of {@code <level>/dimensions/<namespace>/<key>} on every later boot. A persistent world
|
||||
* therefore has to be born under the startup name, which is what {@link #ofPersistentKey} does.</p>
|
||||
*/
|
||||
public final class WorldCreatorCompat {
|
||||
private static volatile boolean keyedCreatorsUnavailable;
|
||||
private static volatile boolean creatorNamesUnwritable;
|
||||
private static volatile Field creatorNameField;
|
||||
|
||||
private WorldCreatorCompat() {
|
||||
}
|
||||
|
||||
public static WorldCreator ofKey(NamespacedKey worldKey) {
|
||||
return ofKey(worldKey, IrisWorldStorage.logicalName(worldKey));
|
||||
}
|
||||
|
||||
public static WorldCreator ofKey(NamespacedKey worldKey, String fallbackWorldName) {
|
||||
WorldCreator keyedCreator = keyedCreator(worldKey);
|
||||
if (keyedCreator != null) {
|
||||
return keyedCreator;
|
||||
}
|
||||
return new WorldCreator(fallbackWorldName);
|
||||
return new WorldCreator(IrisWorldStorage.logicalName(worldKey));
|
||||
}
|
||||
|
||||
public static WorldCreator ofKey(NamespacedKey worldKey, String worldName) {
|
||||
String name = requireWorldName(worldName);
|
||||
WorldCreator keyedCreator = keyedCreator(worldKey);
|
||||
if (keyedCreator == null) {
|
||||
return new WorldCreator(name);
|
||||
}
|
||||
renameCreator(keyedCreator, name);
|
||||
return keyedCreator;
|
||||
}
|
||||
|
||||
public static WorldCreator ofPersistentKey(NamespacedKey worldKey) {
|
||||
WorldCreator keyedCreator = keyedCreator(worldKey);
|
||||
if (keyedCreator != null) {
|
||||
return keyedCreator;
|
||||
}
|
||||
return new WorldCreator(fallbackPersistentName(worldKey, IrisWorldStorage.levelRoot().getName()));
|
||||
return ofKey(worldKey, persistentWorldName(worldKey, IrisWorldStorage.levelRoot().getName()));
|
||||
}
|
||||
|
||||
public static File persistentDimensionRoot(NamespacedKey worldKey) {
|
||||
@@ -75,18 +87,10 @@ public final class WorldCreatorCompat {
|
||||
);
|
||||
}
|
||||
|
||||
static String fallbackName(NamespacedKey worldKey, String levelName) {
|
||||
return IrisWorldStorage.logicalName(worldKey, levelName);
|
||||
}
|
||||
|
||||
static String fallbackPersistentName(NamespacedKey worldKey, String levelName) {
|
||||
static String persistentWorldName(NamespacedKey worldKey, String levelName) {
|
||||
return IrisWorldStorage.configuredWorldName(worldKey, levelName);
|
||||
}
|
||||
|
||||
static NamespacedKey fallbackKey(String creatorName, String levelName) {
|
||||
return IrisWorldStorage.keyFromConfiguredWorldName(creatorName, levelName);
|
||||
}
|
||||
|
||||
private static WorldCreator keyedCreator(NamespacedKey worldKey) {
|
||||
if (keyedCreatorsUnavailable) {
|
||||
return null;
|
||||
@@ -98,4 +102,42 @@ public final class WorldCreatorCompat {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WorldCreator rejects a name and a key passed together and derives the name from the key, so the
|
||||
* only way to hand a keyed world its startup name at creation is to overwrite the creator's name.
|
||||
* If that ever stops working the keyed creator is still correct except for the name, which is what
|
||||
* shipped before, so the world is created rather than refused.
|
||||
*/
|
||||
private static void renameCreator(WorldCreator creator, String worldName) {
|
||||
if (worldName.equals(creator.name()) || creatorNamesUnwritable) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
creatorNameField().set(creator, worldName);
|
||||
} catch (ReflectiveOperationException | RuntimeException | Error e) {
|
||||
creatorNamesUnwritable = true;
|
||||
IrisLogging.warn("WorldCreator name is not writable; world %s is created as %s and renamed on the next boot.",
|
||||
worldName, creator.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static Field creatorNameField() throws ReflectiveOperationException {
|
||||
Field cached = creatorNameField;
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
Field name = WorldCreator.class.getDeclaredField("name");
|
||||
name.setAccessible(true);
|
||||
creatorNameField = name;
|
||||
return name;
|
||||
}
|
||||
|
||||
private static String requireWorldName(String worldName) {
|
||||
String name = Objects.requireNonNull(worldName, "worldName").trim();
|
||||
if (name.isEmpty()) {
|
||||
throw new IllegalArgumentException("World name cannot be empty.");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,11 @@
|
||||
|
||||
package art.arcane.iris.core.link;
|
||||
|
||||
import art.arcane.iris.core.IrisWorldStorage;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import lombok.SneakyThrows;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.mvplugins.multiverse.core.MultiverseCoreApi;
|
||||
import org.mvplugins.multiverse.core.utils.result.Attempt;
|
||||
@@ -30,57 +33,146 @@ import org.mvplugins.multiverse.core.world.options.RemoveWorldOptions;
|
||||
import org.mvplugins.multiverse.core.world.reasons.RemoveFailureReason;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Multiverse indexes its world store by NamespacedKey and by the Bukkit world name it saw when the
|
||||
* world was imported, and records that name as {@code legacy-world-name}. Iris creates and registers
|
||||
* its worlds under the Paper startup name ({@code <level>_<namespace>_<key>}), so the name Multiverse
|
||||
* sees at import is already the name it will see again after every restart, and it is the name every
|
||||
* Iris unregistration uses.
|
||||
*/
|
||||
public class MultiverseCoreLink {
|
||||
public boolean removeFromConfig(World world) {
|
||||
return removeFromConfig(world.getName());
|
||||
}
|
||||
|
||||
public boolean removeFromConfig(String world) {
|
||||
/**
|
||||
* Best-effort scrub for worlds Iris never registered (studio worlds). Multiverse not knowing the
|
||||
* world is the normal case here, so a miss is not worth a warning.
|
||||
*/
|
||||
public boolean removeIfPresent(World world) {
|
||||
String worldName = Objects.requireNonNull(world, "world").getName();
|
||||
if (!isActive()) {
|
||||
return false;
|
||||
}
|
||||
WorldManager manager = worldManager();
|
||||
MultiverseWorld multiverseWorld = manager.getWorld(world).getOrElse((MultiverseWorld) null);
|
||||
MultiverseWorld multiverseWorld = resolve(manager, worldName);
|
||||
if (multiverseWorld == null) {
|
||||
return false;
|
||||
}
|
||||
return remove(manager, multiverseWorld, worldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authoritative unregistration for a world Iris registered. A miss leaves a ghost entry behind in
|
||||
* Multiverse's worlds.yml, so it is reported.
|
||||
*/
|
||||
public boolean removeFromConfig(String configuredWorldName) {
|
||||
String worldName = requireWorldName(configuredWorldName);
|
||||
if (!isActive()) {
|
||||
IrisLogging.debug("Multiverse is not enabled; skipped unregistering \"" + worldName + "\".");
|
||||
return false;
|
||||
}
|
||||
WorldManager manager = worldManager();
|
||||
MultiverseWorld multiverseWorld = resolve(manager, worldName);
|
||||
if (multiverseWorld == null) {
|
||||
IrisLogging.warn("Multiverse has no world registered as %s; its worlds.yml entry was left as-is.",
|
||||
worldName);
|
||||
return false;
|
||||
}
|
||||
return remove(manager, multiverseWorld, worldName);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void updateWorld(World bukkitWorld, String configuredWorldName, String pack) {
|
||||
World world = Objects.requireNonNull(bukkitWorld, "bukkitWorld");
|
||||
String worldName = requireWorldName(configuredWorldName);
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
if (!worldName.equals(world.getName())) {
|
||||
// Multiverse records the live name as legacy-world-name. A live name that is not the
|
||||
// startup name makes it re-import the world next boot and collide with its own config key.
|
||||
IrisLogging.warn("World %s is live as %s; Multiverse will record the live name.",
|
||||
worldName, world.getName());
|
||||
}
|
||||
String generator = "Iris:" + pack;
|
||||
WorldManager manager = worldManager();
|
||||
MultiverseWorld multiverseWorld = manager.getWorld(world)
|
||||
.orElse(() -> manager.getWorld(worldName))
|
||||
.getOrElse(() -> {
|
||||
// Import through the live world so Multiverse binds its own config key to the key
|
||||
// Paper gave the world and records the startup name the world was created under.
|
||||
ImportWorldOptions options = ImportWorldOptions.worldName(world.getName())
|
||||
.generator(generator)
|
||||
.environment(world.getEnvironment())
|
||||
.useSpawnAdjust(false);
|
||||
return manager.importWorld(options).get();
|
||||
});
|
||||
|
||||
multiverseWorld.setAutoLoad(false);
|
||||
if (!generator.equals(multiverseWorld.getGenerator())) {
|
||||
setWorldConfigString(multiverseWorld, "setGenerator", generator);
|
||||
}
|
||||
|
||||
manager.saveWorldsConfig().get();
|
||||
}
|
||||
|
||||
private boolean remove(WorldManager manager, MultiverseWorld multiverseWorld, String worldName) {
|
||||
Attempt<String, RemoveFailureReason> removal = manager.removeWorld(RemoveWorldOptions.world(multiverseWorld));
|
||||
if (removal.isFailure()) {
|
||||
throw new IllegalStateException("Multiverse refused to remove world \"" + world + "\": "
|
||||
throw new IllegalStateException("Multiverse refused to remove world \"" + worldName + "\": "
|
||||
+ removal.getFailureMessage());
|
||||
}
|
||||
manager.saveWorldsConfig().get();
|
||||
return true;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void updateWorld(World bukkitWorld, String pack) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
private MultiverseWorld resolve(WorldManager manager, String worldName) {
|
||||
for (String candidate : lookupNames(worldName, IrisWorldStorage.levelRoot().getName())) {
|
||||
MultiverseWorld multiverseWorld = manager.getWorld(candidate).getOrElse((MultiverseWorld) null);
|
||||
if (multiverseWorld != null) {
|
||||
return multiverseWorld;
|
||||
}
|
||||
}
|
||||
String generator = "Iris:" + pack;
|
||||
WorldManager manager = worldManager();
|
||||
MultiverseWorld multiverseWorld = manager.getWorld(bukkitWorld).getOrElse(() -> {
|
||||
ImportWorldOptions options = ImportWorldOptions.worldName(bukkitWorld.getName())
|
||||
.generator(generator)
|
||||
.environment(bukkitWorld.getEnvironment())
|
||||
.useSpawnAdjust(false);
|
||||
return manager.importWorld(options).get();
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
multiverseWorld.setAutoLoad(false);
|
||||
if (!generator.equals(multiverseWorld.getGenerator())) {
|
||||
Field field = MultiverseWorld.class.getDeclaredField("worldConfig");
|
||||
field.setAccessible(true);
|
||||
|
||||
Object config = field.get(multiverseWorld);
|
||||
config.getClass()
|
||||
.getDeclaredMethod("setGenerator", String.class)
|
||||
.invoke(config, generator);
|
||||
/**
|
||||
* worlds.yml files written by Iris builds that created keyed worlds under {@code <namespace>_<key>}
|
||||
* still carry that name, so a startup name must also resolve through the Multiverse config key it
|
||||
* encodes or those entries can never be removed.
|
||||
*/
|
||||
static List<String> lookupNames(String worldName, String levelName) {
|
||||
List<String> candidates = new ArrayList<>(2);
|
||||
candidates.add(worldName);
|
||||
try {
|
||||
NamespacedKey key = IrisWorldStorage.managedKeyFromName(worldName, levelName);
|
||||
if (IrisWorldStorage.configuredWorldName(key, levelName).equals(worldName)) {
|
||||
candidates.add(key.toString());
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// Not an Iris startup name; the plain name is the only identity Multiverse can have.
|
||||
}
|
||||
return List.copyOf(candidates);
|
||||
}
|
||||
|
||||
manager.saveWorldsConfig().get();
|
||||
private static void setWorldConfigString(MultiverseWorld world, String setter, String value) throws Exception {
|
||||
Field field = MultiverseWorld.class.getDeclaredField("worldConfig");
|
||||
field.setAccessible(true);
|
||||
|
||||
Object config = field.get(world);
|
||||
Method method = config.getClass().getDeclaredMethod(setter, String.class);
|
||||
method.setAccessible(true);
|
||||
method.invoke(config, value);
|
||||
}
|
||||
|
||||
private static String requireWorldName(String worldName) {
|
||||
String name = Objects.requireNonNull(worldName, "worldName").trim();
|
||||
if (name.isEmpty()) {
|
||||
throw new IllegalArgumentException("World name cannot be empty.");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private WorldManager worldManager() {
|
||||
|
||||
@@ -750,7 +750,7 @@ public final class StudioOpenCoordinator {
|
||||
for (World loadedWorld : loadedWorlds) {
|
||||
CompletableFuture<Boolean> unload = J.sfut(() ->
|
||||
IrisServices.get(MultiverseCoreLink.class)
|
||||
.removeFromConfig(loadedWorld))
|
||||
.removeIfPresent(loadedWorld))
|
||||
.thenCompose(ignored -> WorldLifecycleService.get().unloadAsync(loadedWorld, false));
|
||||
unloads.add(unload);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public final class WorldRuntimeControlService {
|
||||
return false;
|
||||
}
|
||||
|
||||
IrisServices.get(art.arcane.iris.core.link.MultiverseCoreLink.class).removeFromConfig(world);
|
||||
IrisServices.get(art.arcane.iris.core.link.MultiverseCoreLink.class).removeIfPresent(world);
|
||||
setIntGameRule(world, 0, "SPAWN_CHUNK_RADIUS", "spawnChunkRadius");
|
||||
enableStudioEntitySpawning(world);
|
||||
if (!IrisSettings.get().getStudio().isDisableTimeAndWeather()) {
|
||||
|
||||
@@ -331,16 +331,23 @@ public class IrisCreator {
|
||||
reportCreationProgress(creationReporter, 0.84D, "register_world");
|
||||
|
||||
if (!studio && !benchmark) {
|
||||
// bukkit.yml and Multiverse must agree on the startup name, or Multiverse re-imports the
|
||||
// world on the next boot and collides with its own config key.
|
||||
String registrationName = IrisWorldStorage.configuredWorldName(
|
||||
worldKey,
|
||||
IrisWorldStorage.levelRoot().getName()
|
||||
);
|
||||
BukkitWorldConfiguration.register(
|
||||
BUKKIT_YML,
|
||||
IrisWorldStorage.configuredWorldName(worldKey, IrisWorldStorage.levelRoot().getName()),
|
||||
registrationName,
|
||||
dimension,
|
||||
seed
|
||||
);
|
||||
bukkitRegistered = true;
|
||||
World createdWorld = world;
|
||||
CompletableFuture<Void> multiverseRegistration = J.sfut(
|
||||
() -> IrisServices.get(MultiverseCoreLink.class).updateWorld(createdWorld, dimension)
|
||||
() -> IrisServices.get(MultiverseCoreLink.class)
|
||||
.updateWorld(createdWorld, registrationName, dimension)
|
||||
);
|
||||
if (multiverseRegistration == null) {
|
||||
throw new IrisException("Failed to schedule Multiverse registration for world \"" + name + "\".");
|
||||
|
||||
Reference in new issue
Block a user