Compare commits

...
11 Commits
Author SHA1 Message Date
Ben Woo 34185b90a9 Merge pull request #3524 from Multiverse/chore/ignore-kolin
Add Kolin build cache folder to gitignore
2026-10-19 13:17:52 +08:00
Ben Woo 5d57bbfda9 Add Kolin build cache folder to gitignore 2026-10-19 11:14:11 +08:00
Ben Woo a78459ab77 Merge pull request #3523 from Multiverse/fix/update-legacy-name
Update legacy world name on world load
2026-10-19 11:06:04 +08:00
Ben Woo 0ad795b27f Update legacy world name on world load
This should elevate any issues of legacy world name being desynced with actual world name.
2026-10-19 10:57:18 +08:00
Ben Woo 2765cf6f3e Merge pull request #3522 from Multiverse/fix/string-parse
Fix string parse for special `@` values
2026-10-19 10:47:51 +08:00
Ben Woo 7a9b21f9a0 Fix command input not allowing @vault-economy currency type 2026-10-19 10:43:25 +08:00
Ben Woo f55ceda42c Fix command input not allowing @unset, @bukkit spawn values 2026-10-19 10:42:42 +08:00
Ben Woo 893c69a6e1 Merge pull request #3521 from Multiverse/chore/async-fail
Clearer message that fail async teleport is not a Multiverse bug
2026-10-09 14:09:39 +08:00
Ben Woo 6bb93766d4 Clearer message that fail async teleport is not a Multiverse bug 2026-10-09 14:03:06 +08:00
Ben Woo fe49efe276 Merge pull request #3520 from Multiverse/chore/simplify-pr-comment
Simplify artifact PR comment message
2026-09-27 23:26:18 +08:00
Ben Woo 8de11b59a1 Simplify artifact PR comment message 2026-09-27 23:22:25 +08:00
10 changed files with 115 additions and 11 deletions

No files matched your search

+3 -10
View File
@@ -71,14 +71,7 @@ jobs:
message: |
### ✅ `${{ inputs.plugin_name }}` test build ready
The build and tests passed. This PR build is ready to try.
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 }})
**[⬇️ 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>
View [CI build and checks](${{ github.event.workflow_run.html_url }}) for more details.
+3
View File
@@ -46,3 +46,6 @@ debug.txt
# Gradle
.gradle
# Kotlin generated files
/.kotlin/
@@ -26,6 +26,7 @@ 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,7 +264,8 @@ 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", teleportee.getName(), location);
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);
return Attempt.failure(TeleportFailureReason.TELEPORT_FAILED);
});
}
@@ -55,6 +55,7 @@ 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) {
@@ -6,6 +6,7 @@ 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;
@@ -26,6 +27,9 @@ 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;
@@ -35,6 +39,8 @@ 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.
*/
@@ -169,11 +175,22 @@ 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;
@@ -306,6 +323,8 @@ 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)
@@ -120,6 +120,29 @@ 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,6 +9,7 @@ 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;
@@ -16,13 +17,19 @@ 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;
@@ -177,6 +184,7 @@ 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) -> {
@@ -193,6 +201,7 @@ 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) -> {
@@ -215,6 +224,27 @@ 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,6 +7,7 @@ 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.
@@ -49,6 +49,38 @@ class WorldManagerTest : TestWithMockBukkit() {
assertNotNull(world2)
}
@Test
fun `Legacy world name changes update loaded and unloaded lookups`() {
val worldKey = world.key.toString()
assertTrue(world.worldConfig.setLegacyWorldName("renamed_world").isSuccess)
assertFalse(worldManager.getWorld("world").isDefined)
assertSame(world, worldManager.getLoadedWorld("RENAMED_WORLD").get())
assertSame(world, worldManager.getLoadedWorld(worldKey).get())
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)
}
@Test
fun `Setting up loaded world config refreshes legacy world name lookups`() {
assertTrue(world.worldConfig.setLegacyWorldName("stale_name").isSuccess)
assertTrue(world.worldConfig.save().isSuccess)
world.setWorldConfig(world.worldConfig)
assertEquals("world", world.name)
assertSame(world, worldManager.getLoadedWorld("world").get())
assertFalse(worldManager.getWorld("stale_name").isDefined)
assertTrue(worldManager.unloadWorld(UnloadWorldOptions.world(world)).isSuccess)
assertTrue(worldManager.getUnloadedWorld("world").isDefined)
assertFalse(worldManager.getWorld("stale_name").isDefined)
}
@Test
fun `Create world with custom options`() {
assertTrue(worldManager.createWorld(