Compare commits

..
1 Commits
Author SHA1 Message Date
Ben Woo 8f19be1671 Add world ref api to handle stored world references for later use 2026-09-27 22:54:19 +08:00
13 changed files with 147 additions and 142 deletions

No files matched your search

+10 -3
View File
@@ -71,7 +71,14 @@ jobs:
message: |
### ✅ `${{ inputs.plugin_name }}` test build ready
Last updated for: [${{ github.event.workflow_run.head_sha }}](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.event.workflow_run.head_sha }})
**Download:** [${{ inputs.plugin_name }}-pr${{ steps.artifact.outputs.number }}.zip](${{ steps.artifact.outputs.url }})
The build and tests passed. This PR build is ready to try.
View [CI build and checks](${{ github.event.workflow_run.html_url }}) for more details.
**[⬇️ Download test build](${{ steps.artifact.outputs.url }})**
Extract the downloaded ZIP to get the plugin JAR.
| Build details | |
| --- | --- |
| Commit | `${{ github.event.workflow_run.head_sha }}` |
| Workflow | [Run #${{ github.event.workflow_run.run_number }}](${{ github.event.workflow_run.html_url }}) |
<sub>Sign in to GitHub to download. Artifacts expire after the repository's retention period. This comment updates after successful builds.</sub>
-3
View File
@@ -46,6 +46,3 @@ debug.txt
# Gradle
.gradle
# Kotlin generated files
/.kotlin/
@@ -26,7 +26,6 @@ public enum MVCorei18n implements MessageKeyProvider {
CONFIG_STRING_PARSER_INVALIDDOUBLE,
CONFIG_STRING_PARSER_INVALIDFLOAT,
CONFIG_STRING_PARSER_INVALIDLONG,
CONFIG_STRING_PARSER_INVALIDMATERIAL,
// core config
CORE_CONFIG_CUSTOMPORTALSEARCHRADIUS_NONNEGATIVE,
@@ -264,8 +264,7 @@ public final class AsyncSafetyTeleporterAction {
Logging.finer("Teleported async %s to %s", teleportee.getName(), location);
return Attempt.success(null);
}
Logging.warning("Failed to async teleport %s to %s. This is usually caused by another " +
"plugin canceling the teleport event and not a bug with Multiverse.", teleportee.getName(), location);
Logging.warning("Failed to async teleport %s to %s", teleportee.getName(), location);
return Attempt.failure(TeleportFailureReason.TELEPORT_FAILED);
});
}
@@ -13,7 +13,7 @@ import org.bukkit.WorldType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
@@ -22,6 +22,11 @@ import org.mvplugins.multiverse.core.world.location.SpawnLocation;
/**
* Extension of {@link MultiverseWorld} that represents a world that is currently loaded with bukkit world object.
* <p>
* Do not store instances of this class for future use during runtime. Worlds can be unloaded, removed, reloaded,
* or regenerated, making stored instances stale. Instead, store a {@link MultiverseWorldRef} obtained from
* {@link #asRef()} and call {@link MultiverseWorldRef#getLoaded()} when a loaded world is needed. The reference
* returns an empty Option when the world is unloaded or no longer managed by Multiverse.
*/
public final class LoadedMultiverseWorld extends MultiverseWorld {
@@ -34,16 +39,13 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
LoadedMultiverseWorld(
@NotNull World world,
@NotNull WorldConfig worldConfig,
@NotNull CoreConfig config,
@NotNull BlockSafety blockSafety,
@NotNull LocationManipulation locationManipulation,
@NotNull EntityPurger entityPurger
) {
super(worldConfig, config);
@NotNull MultiverseCore multiverseCore
) {
super(worldConfig, multiverseCore);
this.worldUid = world.getUID();
this.blockSafety = blockSafety;
this.locationManipulation = locationManipulation;
this.entityPurger = entityPurger;
this.blockSafety = multiverseCore.getServiceLocator().getService(BlockSafety.class);
this.locationManipulation = multiverseCore.getServiceLocator().getService(LocationManipulation.class);
this.entityPurger = multiverseCore.getServiceLocator().getService(EntityPurger.class);
setupWorldConfig(world);
setupSpawnLocation(world);
@@ -55,7 +57,6 @@ public final class LoadedMultiverseWorld extends MultiverseWorld {
worldConfig.load();
worldConfig.setEnvironment(world.getEnvironment());
worldConfig.setSeed(world.getSeed());
worldConfig.setLegacyWorldName(world.getName());
}
private void setupSpawnLocation(World world) {
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnmodifiableView;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
@@ -29,6 +30,11 @@ import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
/**
* Represents a world handled by Multiverse which has all the custom properties provided by Multiverse.
* <p>
* Do not store instances of this class for future use during runtime. Worlds can be unloaded, removed, reloaded,
* or regenerated, making stored instances stale. Instead, store a {@link MultiverseWorldRef} obtained from
* {@link #asRef()} and retrieve the current world instance when needed. The reference returns an empty Option
* when the world is no longer available.
*/
public sealed class MultiverseWorld permits LoadedMultiverseWorld {
/**
@@ -37,11 +43,13 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
WorldConfig worldConfig;
protected final CoreConfig config;
private final WorldStore worldStore;
private String colourlessAlias = "";
MultiverseWorld(WorldConfig worldConfig, CoreConfig config) {
MultiverseWorld(@NotNull WorldConfig worldConfig, @NotNull MultiverseCore multiverseCore) {
this.worldConfig = worldConfig;
this.config = config;
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
this.worldStore = multiverseCore.getServiceLocator().getService(WorldStore.class);
this.worldConfig.setMVWorld(this);
updateColourlessAlias();
}
@@ -125,6 +133,22 @@ public sealed class MultiverseWorld permits LoadedMultiverseWorld {
.map(world -> (LoadedMultiverseWorld) world);
}
/**
* Creates a reference to this world's key for storing and retrieving the current world instance at runtime.
* <p>
* Store this reference when retaining a world across reloads or regeneration, which make stored
* {@link MultiverseWorld} and {@link LoadedMultiverseWorld} instances stale. Use {@link MultiverseWorldRef#get()}
* or {@link MultiverseWorldRef#getLoaded()} to retrieve the current instance when needed.
*
* @return A reference that looks up the current world instance using this world's key.
*
* @since 5.9
*/
@ApiStatus.AvailableSince("5.9")
public MultiverseWorldRef asRef() {
return new MultiverseWorldRef(getKey(), worldStore);
}
/**
* Gets the properties handler of this world.
*
@@ -0,0 +1,57 @@
package org.mvplugins.multiverse.core.world;
import io.vavr.control.Option;
import org.bukkit.NamespacedKey;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* A reference to a world by its key that can be stored and used to retrieve the current world instance at runtime.
* <p>
* Stored {@link MultiverseWorld} and {@link LoadedMultiverseWorld} instances become stale when a world is reloaded
* or regenerated. This reference looks up the world on each call to {@link #get()} or {@link #getLoaded()}, allowing
* callers to keep the reference across those operations and retrieve the current instance when needed.
* Store this reference rather than caching the world instances returned by its methods.
* <p>
* Obtain a reference using {@link MultiverseWorld#asRef()}. Resolving a reference does not load the world.
*
* @since 5.9
*/
@ApiStatus.AvailableSince("5.9")
public class MultiverseWorldRef {
private final NamespacedKey worldKey;
private final WorldStore worldStore;
MultiverseWorldRef(@NotNull NamespacedKey worldKey, @NotNull WorldStore worldStore) {
this.worldKey = worldKey;
this.worldStore = worldStore;
}
/**
* Gets the current world instance for this reference's key, whether loaded or unloaded.
* The world is looked up on each call so the returned instance reflects reloads and regeneration.
*
* @return The current world instance wrapped in an Option, or None if the world is no longer managed by Multiverse.
*
* @since 5.9
*/
@ApiStatus.AvailableSince("5.9")
public Option<MultiverseWorld> get() {
return worldStore.getWorld(worldKey);
}
/**
* Gets the current loaded world instance for this reference's key without loading the world.
* The world is looked up on each call so the returned instance reflects reloads and regeneration.
*
* @return The current loaded world instance wrapped in an Option, or None if the world is unloaded or no longer
* managed by Multiverse.
*
* @since 5.9
*/
@ApiStatus.AvailableSince("5.9")
public Option<LoadedMultiverseWorld> getLoaded() {
return worldStore.getLoadedWorld(worldKey);
}
}
@@ -6,7 +6,6 @@ import java.util.Objects;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Option;
import io.vavr.control.Try;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Difficulty;
@@ -27,9 +26,6 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode;
import org.mvplugins.multiverse.core.config.node.ListConfigNode;
import org.mvplugins.multiverse.core.config.node.NodeGroup;
import org.mvplugins.multiverse.core.economy.MVEconomist;
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.utils.MaterialConverter;
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
import org.mvplugins.multiverse.core.world.helpers.AliasNameConflictChecker;
@@ -39,8 +35,6 @@ import org.mvplugins.multiverse.core.world.location.NullSpawnLocation;
import org.mvplugins.multiverse.core.world.location.SpawnLocation;
import org.mvplugins.multiverse.core.world.entity.EntitySpawnConfig;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
/**
* Represents nodes in a world configuration.
*/
@@ -49,6 +43,7 @@ final class WorldConfigNodes {
private final NodeGroup nodes = new NodeGroup();
private WorldManager worldManager;
private WorldStore worldStore;
private EnforcementHandler enforcementHandler;
private CoreConfig config;
private AliasNameConflictChecker aliasNameConflictChecker;
@@ -58,6 +53,7 @@ final class WorldConfigNodes {
WorldConfigNodes(@NotNull MultiverseCore multiverseCore, @NotNull WorldKeyOrName keyOrName) {
this.worldManager = multiverseCore.getServiceLocator().getService(WorldManager.class);
this.worldStore = multiverseCore.getServiceLocator().getService(WorldStore.class);
this.enforcementHandler = multiverseCore.getServiceLocator().getService(EnforcementHandler.class);
this.config = multiverseCore.getServiceLocator().getService(CoreConfig.class);
this.aliasNameConflictChecker = multiverseCore.getServiceLocator().getService(AliasNameConflictChecker.class);
@@ -106,7 +102,7 @@ final class WorldConfigNodes {
final ConfigNode<String> alias = node(ConfigNode.builder("alias", String.class)
.defaultValue("")
.onLoadAndChange((sender, oldValue, newValue) -> {
worldManager.getWorldStore().changeAlias(
worldStore.changeAlias(
ChatTextFormatter.removeColor(oldValue),
ChatTextFormatter.removeColor(newValue),
keyOrName.usableKey()
@@ -175,22 +171,11 @@ final class WorldConfigNodes {
final ConfigNode<Material> entryFeeCurrency = node(ConfigNode.builder("entry-fee.currency", Material.class)
.defaultValue(MVEconomist.VAULT_ECONOMY_MATERIAL)
.name("entryfee-currency")
.stringParser((value, clazz) -> {
if (value != null && value.equalsIgnoreCase(MVEconomist.VAULT_ECONOMY_CODE)) {
return Try.success(MVEconomist.VAULT_ECONOMY_MATERIAL);
}
return Try.of(() -> MaterialConverter.stringToMaterial(value))
.filter(Objects::nonNull)
.orElse(Try.failure(new MultiverseException(Message.of(
MVCorei18n.CONFIG_STRING_PARSER_INVALIDMATERIAL,
replace("{input}").with(value)))));
})
.serializer(new NodeSerializer<>() {
@Override
public Material deserialize(Object object, Class<Material> type) {
return Option.of(object)
.map(String::valueOf)
.filter(s -> !s.isBlank())
.map(materialStr -> {
if (materialStr.equalsIgnoreCase(MVEconomist.VAULT_ECONOMY_CODE)) {
return MVEconomist.VAULT_ECONOMY_MATERIAL;
@@ -323,8 +308,6 @@ final class WorldConfigNodes {
.hidden());
final ConfigNode<String> legacyWorldName = node(ConfigNode.builder("read-only.legacy-world-name", String.class)
.onLoadAndChange((oldValue, newValue) -> worldManager.getWorldStore().changeLegacyWorldName(
oldValue, newValue, keyOrName.usableKey()))
.hidden());
final ConfigNode<Long> seed = node(ConfigNode.builder("read-only.seed", Long.class)
@@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.event.world.MVWorldClonedEvent;
import org.mvplugins.multiverse.core.event.world.MVWorldCreatedEvent;
@@ -45,8 +46,6 @@ import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
import org.mvplugins.multiverse.core.permissions.CorePermissions;
import org.mvplugins.multiverse.core.teleportation.BlockSafety;
import org.mvplugins.multiverse.core.teleportation.LocationManipulation;
import org.mvplugins.multiverse.core.utils.ServerProperties;
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
import org.mvplugins.multiverse.core.utils.compatibility.WorldCompatibility;
@@ -56,7 +55,6 @@ import org.mvplugins.multiverse.core.utils.result.FailureReason;
import org.mvplugins.multiverse.core.utils.FileUtils;
import org.mvplugins.multiverse.core.utils.text.ChatTextFormatter;
import org.mvplugins.multiverse.core.world.biomeprovider.BiomeProviderFactory;
import org.mvplugins.multiverse.core.world.entity.EntityPurger;
import org.mvplugins.multiverse.core.world.generators.GeneratorProvider;
import org.mvplugins.multiverse.core.world.helpers.DataStore.GameRulesStore;
import org.mvplugins.multiverse.core.world.helpers.DataTransfer;
@@ -96,6 +94,7 @@ public final class WorldManager {
"data/paper/metadata.dat" // New papermc format for 26.1+
);
private final MultiverseCore multiverseCore;
private final WorldStore worldStore;
private final List<String> unloadTracker;
private final List<String> loadTracker;
@@ -104,44 +103,37 @@ public final class WorldManager {
private final BiomeProviderFactory biomeProviderFactory;
private final GeneratorProvider generatorProvider;
private final FileUtils fileUtils;
private final BlockSafety blockSafety;
private final LocationManipulation locationManipulation;
private final PluginManager pluginManager;
private final CorePermissions corePermissions;
private final ServerProperties serverProperties;
private final CoreConfig config;
private final EntityPurger entityPurger;
private final Provider<PotentialWorldFinder> potentialWorldFinder;
@Inject
WorldManager(
@NotNull MultiverseCore multiverseCore,
@NotNull WorldStore worldStore,
@NotNull WorldsConfigManager worldsConfigManager,
@NotNull WorldNameChecker worldNameChecker,
@NotNull BiomeProviderFactory biomeProviderFactory,
@NotNull GeneratorProvider generatorProvider,
@NotNull FileUtils fileUtils,
@NotNull BlockSafety blockSafety,
@NotNull LocationManipulation locationManipulation,
@NotNull PluginManager pluginManager,
@NotNull CorePermissions corePermissions,
@NotNull ServerProperties serverProperties,
@NotNull CoreConfig config,
@NotNull EntityPurger entityPurger,
@NotNull Provider<PotentialWorldFinder> potentialWorldFinder) {
this.multiverseCore = multiverseCore;
this.worldStore = worldStore;
this.worldsConfigManager = worldsConfigManager;
this.worldNameChecker = worldNameChecker;
this.biomeProviderFactory = biomeProviderFactory;
this.generatorProvider = generatorProvider;
this.fileUtils = fileUtils;
this.blockSafety = blockSafety;
this.locationManipulation = locationManipulation;
this.pluginManager = pluginManager;
this.corePermissions = corePermissions;
this.serverProperties = serverProperties;
this.config = config;
this.entityPurger = entityPurger;
this.potentialWorldFinder = potentialWorldFinder;
this.unloadTracker = new ArrayList<>();
@@ -417,7 +409,7 @@ public final class WorldManager {
}
private MultiverseWorld newMultiverseWorld(WorldConfig worldConfig) {
MultiverseWorld mvWorld = new MultiverseWorld(worldConfig, config);
MultiverseWorld mvWorld = new MultiverseWorld(worldConfig, multiverseCore);
worldStore.putUnloadedWorld(mvWorld);
corePermissions.addWorldPermissions(mvWorld);
return mvWorld;
@@ -456,10 +448,7 @@ public final class WorldManager {
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
world,
worldConfig,
config,
blockSafety,
locationManipulation,
entityPurger
multiverseCore
);
worldStore.putLoadedWorld(loadedWorld);
saveWorldsConfig();
@@ -592,10 +581,7 @@ public final class WorldManager {
LoadedMultiverseWorld loadedWorld = new LoadedMultiverseWorld(
bukkitWorld,
worldConfig,
config,
blockSafety,
locationManipulation,
entityPurger
multiverseCore
);
worldStore.putLoadedWorld(loadedWorld);
saveWorldsConfig();
@@ -1278,10 +1264,6 @@ public final class WorldManager {
});
}
WorldStore getWorldStore() {
return worldStore;
}
/**
* A simple pair wrapper for convenience to pass both the world key or name and the options together between methods
* when parsing world options.
@@ -120,29 +120,6 @@ final class WorldStore {
unloadedList.add(unloadedMap.get(world.getKey().toString()));
}
void changeLegacyWorldName(@Nullable String oldName, @Nullable String newName, @NotNull NamespacedKey worldKey) {
if (Objects.equals(oldName, newName)) {
return;
}
String key = worldKey.toString();
updateLegacyWorldName(loadedMap, oldName, newName, key);
updateLegacyWorldName(unloadedMap, oldName, newName, key);
}
private <T extends MultiverseWorld> void updateLegacyWorldName(
Map<String, T> map, @Nullable String oldName, @Nullable String newName, String key) {
T world = map.get(key);
if (world == null) {
return;
}
if (oldName != null && !key.equalsIgnoreCase(oldName)) {
map.remove(oldName, world);
}
if (newName != null) {
map.put(newName, world);
}
}
void changeAlias(@Nullable String oldAlias, @Nullable String newAlias, @NotNull NamespacedKey worldKey) {
if (Objects.equals(oldAlias, newAlias)) {
// nothing changed, ignore
@@ -9,7 +9,6 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.SpawnCategory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mvplugins.multiverse.core.config.CoreConfig;
import org.mvplugins.multiverse.core.config.handle.MemoryConfigurationHandle;
import org.mvplugins.multiverse.core.config.handle.StringPropertyHandle;
@@ -17,19 +16,13 @@ import org.mvplugins.multiverse.core.config.node.ConfigNode;
import org.mvplugins.multiverse.core.config.node.ListConfigNode;
import org.mvplugins.multiverse.core.config.node.Node;
import org.mvplugins.multiverse.core.config.node.NodeGroup;
import org.mvplugins.multiverse.core.config.node.functions.NodeStringParser;
import org.mvplugins.multiverse.core.config.node.serializer.NodeSerializer;
import org.mvplugins.multiverse.core.exceptions.MultiverseException;
import org.mvplugins.multiverse.core.locale.MVCorei18n;
import org.mvplugins.multiverse.core.locale.message.Message;
import org.mvplugins.multiverse.core.world.LoadedMultiverseWorld;
import org.mvplugins.multiverse.core.world.MultiverseWorld;
import java.util.ArrayList;
import java.util.List;
import static org.mvplugins.multiverse.core.locale.message.MessageReplacement.replace;
public final class SpawnCategoryConfig {
private final CoreConfig config;
@@ -184,7 +177,6 @@ public final class SpawnCategoryConfig {
final ConfigNode<Integer> tickRate = node(ConfigNode.builder("tick-rate", Integer.class)
.defaultValue(-2)
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
.stringParser(SpawnValueStringParser.INSTANCE)
.serializer(SpawnValueSerializer.INSTANCE)
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
.onChange((sender, oldValue, newValue) -> {
@@ -201,7 +193,6 @@ public final class SpawnCategoryConfig {
final ConfigNode<Integer> spawnLimit = node(ConfigNode.builder("spawn-limit", Integer.class)
.defaultValue(-2)
.suggester(input -> List.of("@unset", "@bukkit", "10", "100", "400", "1000"))
.stringParser(SpawnValueStringParser.INSTANCE)
.serializer(SpawnValueSerializer.INSTANCE)
.onLoadAndChange((oldValue, newValue) -> applyConfigToWorld())
.onChange((sender, oldValue, newValue) -> {
@@ -224,27 +215,6 @@ public final class SpawnCategoryConfig {
.build());
}
private static class SpawnValueStringParser implements NodeStringParser<Integer> {
private static final SpawnValueStringParser INSTANCE = new SpawnValueStringParser();
@Override
public @NotNull Try<Integer> parse(@Nullable String input, @NotNull Class<Integer> type) {
String str = String.valueOf(input).trim();
if (str.equalsIgnoreCase("@unset")) {
return Try.success(-2);
}
if (str.equalsIgnoreCase("@bukkit")) {
return Try.success(-1);
}
try {
return Try.success(Integer.parseInt(str));
} catch (NumberFormatException e) {
return Try.failure(new MultiverseException(Message.of(MVCorei18n.CONFIG_STRING_PARSER_INVALIDINTEGER,
replace("{input}").with(input))));
}
}
}
private static class SpawnValueSerializer implements NodeSerializer<Integer> {
private static final SpawnValueSerializer INSTANCE = new SpawnValueSerializer();
@@ -7,7 +7,6 @@ mv-core.config.string.parser.invalidinteger=Unable to convert '{input}' to numbe
mv-core.config.string.parser.invaliddouble=Unable to convert '{input}' to number. (double)
mv-core.config.string.parser.invalidfloat=Unable to convert '{input}' to number. (float)
mv-core.config.string.parser.invalidlong=Unable to convert '{input}' to number. (long)
mv-core.config.string.parser.invalidmaterial='{input}' is not a valid material.
# core config
mv-core.core.config.customportalsearchradius.nonnegative=The value must be greater than or equal to 0.
@@ -50,35 +50,45 @@ class WorldManagerTest : TestWithMockBukkit() {
}
@Test
fun `Legacy world name changes update loaded and unloaded lookups`() {
val worldKey = world.key.toString()
assertTrue(world.worldConfig.setLegacyWorldName("renamed_world").isSuccess)
fun `World reference follows unload and reload`() {
val ref = world2.asRef()
assertSame(world2, ref.get().get())
assertSame(world2, ref.getLoaded().get())
assertFalse(worldManager.getWorld("world").isDefined)
assertSame(world, worldManager.getLoadedWorld("RENAMED_WORLD").get())
assertSame(world, worldManager.getLoadedWorld(worldKey).get())
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world2)).isSuccess)
assertTrue(ref.getLoaded().isEmpty)
val unloaded = ref.get().get()
assertFalse(unloaded.isLoaded)
assertSame(worldManager.getWorld(world2.name).get(), unloaded)
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
assertFalse(worldManager.getWorld("world").isDefined)
assertFalse(worldManager.getLoadedWorld("renamed_world").isDefined)
assertEquals(world.key, worldManager.getUnloadedWorld("RENAMED_WORLD").get().key)
assertEquals(world.key, worldManager.getUnloadedWorld(worldKey).get().key)
val unloadedRef = unloaded.asRef()
assertTrue(worldManager.loadWorld(LoadWorldOptions.world(unloaded)).isSuccess)
val reloaded = worldManager.getLoadedWorld(world2.name).get()
assertNotSame(world2, reloaded)
assertSame(reloaded, ref.get().get())
assertSame(reloaded, ref.getLoaded().get())
assertSame(reloaded, unloadedRef.getLoaded().get())
}
@Test
fun `Setting up loaded world config refreshes legacy world name lookups`() {
assertTrue(world.worldConfig.setLegacyWorldName("stale_name").isSuccess)
assertTrue(world.worldConfig.save().isSuccess)
fun `World reference follows regeneration`() {
val ref = world2.asRef()
assertTrue(worldManager.regenWorld(RegenWorldOptions.world(world2).seed(4321L)).isSuccess)
world.setWorldConfig(world.worldConfig)
val regenerated = ref.getLoaded().get()
assertNotSame(world2, regenerated)
assertSame(worldManager.getLoadedWorld(world2.name).get(), regenerated)
assertSame(regenerated, ref.get().get())
assertEquals(4321L, regenerated.seed)
}
assertEquals("world", world.name)
assertSame(world, worldManager.getLoadedWorld("world").get())
assertFalse(worldManager.getWorld("stale_name").isDefined)
@Test
fun `World reference is empty after removal`() {
val ref = world2.asRef()
assertTrue(worldManager.removeWorld(RemoveWorldOptions.world(world2)).isSuccess)
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
assertTrue(worldManager.getUnloadedWorld("world").isDefined)
assertFalse(worldManager.getWorld("stale_name").isDefined)
assertTrue(ref.get().isEmpty)
assertTrue(ref.getLoaded().isEmpty)
}
@Test