Skip to content

Commit f440587

Browse files
authored
Merge branch 'main' into feat/clone-unloaded
2 parents 5dfb098 + 247685c commit f440587

13 files changed

Lines changed: 186 additions & 23 deletions

File tree

.github/workflows/generic.platform_uploads.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ jobs:
107107
files: '["${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar"]'
108108
name: ${{ steps.release-info.outputs.tag_name }}
109109
changelog: ${{ steps.release-artifact.outputs.body }}
110-
game_versions: 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
110+
game_versions: 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
111111
version_type: ${{ steps.parse-release-type.outputs.release_type }}
112112
loaders: bukkit, spigot, paper, purpur
113113
dependencies: ${{ inputs.modrinth_dependencies }}
@@ -121,7 +121,7 @@ jobs:
121121
changelog: ${{ steps.release-artifact.outputs.body }}
122122
changelog_type: markdown
123123
display_name: ${{ steps.release-info.outputs.tag_name }}
124-
game_versions: 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
124+
game_versions: 26.1.1, 26.1, 1.21.11, 1.21.10, 1.21.9, 1.21.8, 1.21.7, 1.21.6, 1.21.5, 1.21.4, 1.21.3, 1.21.2, 1.21.1, 1.21, 1.20.6, 1.20.5, 1.20.4, 1.20.3, 1.20.2, 1.20.1, 1.20, 1.19.4, 1.19.3, 1.19.2, 1.19.1, 1.19, 1.18.2
125125
release_type: ${{ steps.parse-release-type.outputs.release_type }}
126126
project_relations: ${{ inputs.dbo_project_relations }}
127127
file_path: ${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar
@@ -136,5 +136,5 @@ jobs:
136136
channel: ${{ steps.parse-release-type.outputs.release_type }}
137137
files: '[{"path": "${{ github.workspace }}/${{ inputs.plugin_name }}-${{ steps.release-info.outputs.tag_name }}.jar", "platforms": ["PAPER"]}]'
138138
description: ${{ steps.release-artifact.outputs.body }}
139-
platform_dependencies: '{"PAPER": ["1.18.2-1.21.11"]}'
139+
platform_dependencies: '{"PAPER": ["1.18.2-26.1.1"]}'
140140
plugin_dependencies: ${{ inputs.hangar_plugin_dependencies }}

src/main/java/org/mvplugins/multiverse/core/PlaceholderExpansionHook.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public boolean persist() {
113113
return Option.of(player.getWorld().getName());
114114
} else {
115115
warning("You must specify a world name for non-player placeholders");
116-
return null;
116+
return Option.none();
117117
}
118118
}
119119

@@ -150,6 +150,7 @@ public boolean persist() {
150150
case "gamemode" -> world.getGameMode().toString().toLowerCase();
151151
case "generator" -> world.getGenerator();
152152
case "hunger" -> String.valueOf(world.isHunger());
153+
case "isloaded" -> String.valueOf(world.isLoaded());
153154
case "monstersspawn" -> String.valueOf(world.getEntitySpawnConfig()
154155
.getSpawnCategoryConfig(SpawnCategory.MONSTER)
155156
.isSpawn());

src/main/java/org/mvplugins/multiverse/core/commands/LoadCommand.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import org.mvplugins.multiverse.core.command.MVCommandIssuer;
1717
import org.mvplugins.multiverse.core.command.flag.CommandFlag;
1818
import org.mvplugins.multiverse.core.command.flag.CommandFlagsManager;
19+
import org.mvplugins.multiverse.core.command.flag.FlagBuilder;
1920
import org.mvplugins.multiverse.core.command.flag.ParsedCommandFlags;
20-
import org.mvplugins.multiverse.core.command.flags.RemovePlayerFlags;
2121
import org.mvplugins.multiverse.core.locale.MVCorei18n;
2222
import org.mvplugins.multiverse.core.locale.message.MessageReplacement.Replace;
2323
import org.mvplugins.multiverse.core.world.MultiverseWorld;
@@ -38,7 +38,7 @@ class LoadCommand extends CoreCommand {
3838

3939
@Subcommand("load")
4040
@CommandPermission("multiverse.core.load")
41-
@CommandCompletion("@mvworlds:scope=unloaded")
41+
@CommandCompletion("@mvworlds:scope=unloaded @flags:groupName=" + Flags.NAME)
4242
@Syntax("<world>")
4343
@Description("{@@mv-core.load.description}")
4444
void onLoadCommand(
@@ -49,7 +49,7 @@ void onLoadCommand(
4949
MultiverseWorld world,
5050

5151
@Optional
52-
@Syntax("[--remove-players]")
52+
@Syntax("[--skip-folder-check]")
5353
@Description("")
5454
String[] flagArray) {
5555
ParsedCommandFlags parsedFlags = flags.parse(flagArray);
@@ -67,9 +67,9 @@ void onLoadCommand(
6767
}
6868

6969
@Service
70-
private static final class Flags extends RemovePlayerFlags {
70+
private static final class Flags extends FlagBuilder {
7171

72-
private static final String NAME = "mvunload";
72+
private static final String NAME = "mvload";
7373

7474
@Inject
7575
private Flags(@NotNull CommandFlagsManager flagsManager) {

src/main/java/org/mvplugins/multiverse/core/config/CoreConfigNodes.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,10 @@ private <N extends Node> N node(N node) {
220220

221221
final ConfigNode<PassengerModes> passengerMode = node(ConfigNode.builder("teleport.passenger-mode", PassengerModes.class)
222222
.comment("")
223-
.comment("Configures how passengers and vehicles are handled when an entity is teleported.")
223+
.comment("Configures how passengers and vehicles are handled when an entity is teleported. This config is")
224+
.comment("only applied to /mvtp, join-destination and first-spawn-override. It will not touch any other")
225+
.comment("teleport by other plugins or commands.")
226+
.comment("========= Available options: =========")
224227
.comment(" default: Server will handle passengers and vehicles. On papermc 1.21.10+, all will be retained by default.")
225228
.comment(" On older versions, all will be dismounted if world changes.")
226229
.comment(" dismount_passengers: Passengers will be removed from the parent entity before the teleport.")

src/main/java/org/mvplugins/multiverse/core/listeners/MVPlayerListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ private void handleJoinLocation(Player player) {
244244
}
245245

246246
private void teleportToDestinationOnJoin(Player player, DestinationInstance<?, ?> destination) {
247-
asyncSafetyTeleporter.to(destination).teleportSingle(player)
247+
asyncSafetyTeleporter.to(destination).passengerMode(config.getPassengerMode()).teleportSingle(player)
248248
.onSuccess(result -> Logging.fine("Player %s has been teleported on join",
249249
player.getName()))
250250
.onFailure(failure -> Logging.warning("Failed to teleport player %s on join: %s",
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package org.mvplugins.multiverse.core.utils.compatibility;
2+
3+
import io.vavr.control.Option;
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.NamespacedKey;
6+
import org.bukkit.Server;
7+
import org.bukkit.World;
8+
import org.jetbrains.annotations.ApiStatus;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.mvplugins.multiverse.core.utils.ReflectHelper;
11+
12+
import java.lang.reflect.Method;
13+
import java.nio.file.Path;
14+
15+
/**
16+
* Compatibility class used to handle API changes in {@link Bukkit} class.
17+
*/
18+
@ApiStatus.AvailableSince("5.6")
19+
public final class BukkitCompatibility {
20+
21+
private static final Option<Method> GET_LEVEL_DIRECTORY_METHOD;
22+
private static final Option<Method> GET_WORLD_NAMESPACED_KEY_METHOD;
23+
24+
static {
25+
GET_LEVEL_DIRECTORY_METHOD = Option.of(ReflectHelper.getMethod(Server.class, "getLevelDirectory"));
26+
GET_WORLD_NAMESPACED_KEY_METHOD = Option.of(ReflectHelper.getMethod(Bukkit.class, "getWorld", NamespacedKey.class));
27+
}
28+
29+
/**
30+
* Gets the folder where all the worlds will be store. Before 26.1, all worlds are stored in the root directory
31+
* of the server, which can be obtained by {@link Server#getWorldContainer()}.
32+
* <br />
33+
* After 26.1, PaperMC changed all worlds are stored in the "[level]/dimensions/minecraft" folder under the world
34+
* level directory, which needs to be manually parsed.
35+
*
36+
* @return The location where all the worlds folders should be, depending on server's mc version.
37+
*/
38+
@ApiStatus.AvailableSince("5.6")
39+
@NotNull
40+
public static Path getWorldFoldersDirectory() {
41+
Server server = Bukkit.getServer();
42+
return GET_LEVEL_DIRECTORY_METHOD.map(method -> ReflectHelper.invokeMethod(server, method))
43+
.filter(Path.class::isInstance)
44+
.map(Path.class::cast)
45+
.map(path -> path.resolve("dimensions/minecraft"))
46+
.getOrElse(() -> server.getWorldContainer().toPath());
47+
}
48+
49+
/**
50+
* Check if the world with the given name or namespaced key (e.g. minecraft:overworld) exists,
51+
* and return it if it does.
52+
* <br />
53+
* Note that some default world names have different namespaced key matched with them.
54+
* E.g.: world -> minecraft:overworld, world_nether -> minecraft:the_nether, world_the_end -> minecraft:the_end.
55+
*
56+
* @param nameOrKey Either a name or namespaced key string representation.
57+
* @return The world if it exists
58+
*/
59+
@ApiStatus.AvailableSince("5.6")
60+
@NotNull
61+
public static Option<World> getWorldByNameOrKey(@NotNull String nameOrKey) {
62+
return Option.of(Bukkit.getWorld(nameOrKey))
63+
.orElse(() -> GET_WORLD_NAMESPACED_KEY_METHOD
64+
.map(method -> ReflectHelper.invokeMethod(null, method, NamespacedKey.fromString(nameOrKey)))
65+
.filter(World.class::isInstance)
66+
.map(World.class::cast));
67+
}
68+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Contains classes to handle compatibility due to differing API methods across different server versions and the
3+
* API gaps between PaperMC and Spigot.
4+
*/
5+
package org.mvplugins.multiverse.core.utils.compatibility;

src/main/java/org/mvplugins/multiverse/core/world/WorldConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ void deferenceMVWorld() {
396396
/**
397397
* Migrates the entry fee settings. Assumes entry fee is disabled if currency is not set.
398398
*/
399-
static final class EntryFeeMigrator implements MigratorAction {
399+
private static final class EntryFeeMigrator implements MigratorAction {
400400
@Override
401401
public void migrate(ConfigurationSection config) {
402402
String currency = config.getString("entry-fee.currency", "");

src/main/java/org/mvplugins/multiverse/core/world/WorldManager.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.mvplugins.multiverse.core.utils.CaseInsensitiveStringMap;
5252
import org.mvplugins.multiverse.core.utils.ReflectHelper;
5353
import org.mvplugins.multiverse.core.utils.ServerProperties;
54+
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
5455
import org.mvplugins.multiverse.core.utils.result.Attempt;
5556
import org.mvplugins.multiverse.core.utils.result.FailureReason;
5657
import org.mvplugins.multiverse.core.utils.FileUtils;
@@ -701,6 +702,7 @@ private Attempt<String, DeleteFailureReason> doDeleteWorld(@NotNull LoadedMultiv
701702
private Attempt<File, DeleteFailureReason> validateWorldToDelete(
702703
@NotNull LoadedMultiverseWorld world) {
703704
return world.getBukkitWorld().map(World::getWorldFolder)
705+
.peek(folder -> Logging.finer("World folder for world %s is at: %s", world.getName(), folder.getPath()))
704706
.filter(worldNameChecker::isValidWorldFolder)
705707
.map(this::<File, DeleteFailureReason>worldActionResult)
706708
.getOrElse(() -> {
@@ -768,8 +770,8 @@ private Attempt<CloneWorldOptions, CloneFailureReason> cloneWorldCopyFolder(@Not
768770
loadedWorld.getBukkitWorld().peek(this::saveWorldWithFlush);
769771
});
770772
}
771-
File worldFolder = new File(Bukkit.getWorldContainer(), options.fromWorld().getName());
772-
File newWorldFolder = new File(Bukkit.getWorldContainer(), options.newWorldName());
773+
File worldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(options.fromWorld().getName()).toFile();
774+
File newWorldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(options.newWorldName()).toFile();
773775
return fileUtils.copyFolder(worldFolder, newWorldFolder, CLONE_IGNORE_FILES).fold(
774776
exception -> worldActionResult(CloneFailureReason.COPY_FAILED,
775777
options.fromWorld().getName(), exception),
@@ -827,6 +829,7 @@ public Attempt<LoadedMultiverseWorld, RegenFailureReason> regenWorld(@NotNull Re
827829
Location spawnLocation = world.getSpawnLocation();
828830

829831
CreateWorldOptions createWorldOptions = CreateWorldOptions.worldName(world.getName())
832+
.biome(world.getBiome())
830833
.environment(world.getEnvironment())
831834
.generateStructures(world.canGenerateStructures().getOrElse(true))
832835
.generator(world.getGenerator())
@@ -976,12 +979,12 @@ private void throwUnloadException(World world) throws MultiverseWorldException {
976979
* @return A list of all potential worlds.
977980
*/
978981
public List<String> getPotentialWorlds() {
979-
File[] files = Bukkit.getWorldContainer().listFiles();
982+
File[] files = BukkitCompatibility.getWorldFoldersDirectory().toFile().listFiles();
980983
if (files == null) {
981984
return Collections.emptyList();
982985
}
983986
return Arrays.stream(files)
984-
.filter(file -> !isWorld(file.getName()))
987+
.filter(file -> BukkitCompatibility.getWorldByNameOrKey(file.getName()).isEmpty())
985988
.filter(worldNameChecker::isValidWorldFolder)
986989
.map(File::getName)
987990
.toList();

src/main/java/org/mvplugins/multiverse/core/world/helpers/WorldNameChecker.java

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package org.mvplugins.multiverse.core.world.helpers;
22

33
import java.io.File;
4+
import java.util.List;
45
import java.util.Locale;
56
import java.util.Set;
67

8+
import com.dumptruckman.minecraft.util.Logging;
79
import io.vavr.control.Option;
8-
import org.bukkit.Bukkit;
910
import org.jetbrains.annotations.NotNull;
1011
import org.jetbrains.annotations.Nullable;
1112
import org.jvnet.hk2.annotations.Service;
1213
import org.mvplugins.multiverse.core.utils.REPatterns;
14+
import org.mvplugins.multiverse.core.utils.compatibility.BukkitCompatibility;
1315

1416
/**
1517
* <p>Utility class in helping to check the status of a world name and it's associated world folder.</p>
@@ -29,6 +31,18 @@ public final class WorldNameChecker {
2931
"plugins",
3032
"versions");
3133

34+
private static final List<WorldFolderSchema> WORLD_FOLDER_SCHEMA = List.of(
35+
// OLD
36+
WorldFolderSchema.file("level.dat"),
37+
WorldFolderSchema.folder("DIM1"),
38+
WorldFolderSchema.folder("DIM-1"),
39+
// NEW
40+
WorldFolderSchema.file("paper-world.yml"),
41+
WorldFolderSchema.folder("data"),
42+
WorldFolderSchema.folder("entities"),
43+
WorldFolderSchema.folder("poi"),
44+
WorldFolderSchema.folder("region"));
45+
3246
/**
3347
* Checks if a world name is valid.
3448
*
@@ -105,7 +119,8 @@ public FolderStatus checkFolder(@Nullable String worldName) {
105119
if (worldName == null) {
106120
return FolderStatus.DOES_NOT_EXIST;
107121
}
108-
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
122+
File worldFolder = BukkitCompatibility.getWorldFoldersDirectory().resolve(worldName).toFile();
123+
Logging.finer("Checking valid folder for world '%s' at: '%s'", worldName, worldFolder.getPath());
109124
return checkFolder(worldFolder);
110125
}
111126

@@ -120,7 +135,7 @@ public FolderStatus checkFolder(@Nullable File worldFolder) {
120135
if (worldFolder == null || !worldFolder.exists() || !worldFolder.isDirectory()) {
121136
return FolderStatus.DOES_NOT_EXIST;
122137
}
123-
if (!folderHasDat(worldFolder)) {
138+
if (!folderWorldSchemaCheck(worldFolder)) {
124139
return FolderStatus.NOT_A_WORLD;
125140
}
126141
return FolderStatus.VALID;
@@ -133,9 +148,54 @@ public FolderStatus checkFolder(@Nullable File worldFolder) {
133148
* @param worldFolder The File that may be a world.
134149
* @return True if it looks like a world, else false.
135150
*/
136-
private boolean folderHasDat(@NotNull File worldFolder) {
137-
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(".dat"));
138-
return files != null && files.length > 0;
151+
private boolean folderWorldSchemaCheck(@NotNull File worldFolder) {
152+
return WORLD_FOLDER_SCHEMA.stream()
153+
.filter(schema -> schema.check(worldFolder))
154+
.count() >= 2;
155+
}
156+
157+
/**
158+
* Helper class to check if a file or folder exist.
159+
*/
160+
private interface WorldFolderSchema {
161+
162+
static WorldFolderSchema file(String path) {
163+
return new WorldFile(path);
164+
}
165+
166+
static WorldFolderSchema folder(String path) {
167+
return new WorldFolder(path);
168+
}
169+
170+
boolean check(File worldFolder);
171+
172+
final class WorldFile implements WorldFolderSchema {
173+
private final String path;
174+
175+
private WorldFile(String path) {
176+
this.path = path;
177+
}
178+
179+
@Override
180+
public boolean check(File worldFolder) {
181+
File thisFolder = worldFolder.toPath().resolve(path).toFile();
182+
return thisFolder.exists() && thisFolder.isFile();
183+
}
184+
}
185+
186+
final class WorldFolder implements WorldFolderSchema {
187+
private final String path;
188+
189+
private WorldFolder(String path) {
190+
this.path = path;
191+
}
192+
193+
@Override
194+
public boolean check(File worldFolder) {
195+
File thisFolder = worldFolder.toPath().resolve(path).toFile();
196+
return thisFolder.exists() && thisFolder.isDirectory();
197+
}
198+
}
139199
}
140200

141201
/**

0 commit comments

Comments
 (0)