Implement basic clone command

This commit is contained in:
Ben Woo 2023-09-07 00:07:46 +08:00
parent f056dcdbc9
commit af2fc7c630
No known key found for this signature in database
GPG Key ID: FB2A3645536E12C8
5 changed files with 162 additions and 20 deletions

View File

@ -4,15 +4,15 @@ import co.aikar.commands.CommandIssuer;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Conditions;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.commandtools.MVCommandManager;
import com.onarandombox.MultiverseCore.commandtools.MultiverseCommand;
import com.onarandombox.MultiverseCore.utils.MVCorei18n;
import com.onarandombox.MultiverseCore.worldnew.MVWorld;
import com.onarandombox.MultiverseCore.worldnew.WorldManager;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
@ -21,10 +21,10 @@ import org.jvnet.hk2.annotations.Service;
@CommandAlias("mv")
public class CloneCommand extends MultiverseCommand {
private final MVWorldManager worldManager;
private final WorldManager worldManager;
@Inject
public CloneCommand(@NotNull MVCommandManager commandManager, @NotNull MVWorldManager worldManager) {
public CloneCommand(@NotNull MVCommandManager commandManager, @NotNull WorldManager worldManager) {
super(commandManager);
this.worldManager = worldManager;
}
@ -36,26 +36,17 @@ public class CloneCommand extends MultiverseCommand {
@Description("{@@mv-core.clone.description}")
public void onCloneCommand(CommandIssuer issuer,
@Conditions("worldname:scope=both")
@Syntax("<world>")
@Description("{@@mv-core.clone.world.description}")
String worldName,
MVWorld world,
@Single
@Conditions("worldname:scope=new")
@Syntax("<new world name>")
@Description("{@@mv-core.clone.newWorld.description}")
String newWorldName
) {
issuer.sendInfo(MVCorei18n.CLONE_CLONING,
"{world}", worldName,
"{newWorld}", newWorldName);
if (!this.worldManager.cloneWorld(worldName, newWorldName)) {
issuer.sendError(MVCorei18n.CLONE_FAILED);
return;
}
issuer.sendInfo(MVCorei18n.CLONE_SUCCESS,
"{world}", newWorldName);
issuer.sendInfo(MVCorei18n.CLONE_CLONING, "{world}", world.getName(), "{newWorld}", newWorldName);
worldManager.cloneWorld(world, newWorldName);
issuer.sendInfo(MVCorei18n.CLONE_SUCCESS, "{world}", newWorldName);
}
}

View File

@ -12,6 +12,7 @@ import org.bukkit.Material;
import org.bukkit.World;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
public class OfflineWorld {
@ -32,6 +33,10 @@ public class OfflineWorld {
return worldConfig.hasMVWorld();
}
public Collection<String> getConfigurablePropertyNames() {
return worldConfig.getConfigurablePropertyNames();
}
public Try<Object> getProperty(String name) {
return worldConfig.getProperty(name);
}

View File

@ -10,6 +10,7 @@ import com.onarandombox.MultiverseCore.utils.result.Result;
import com.onarandombox.MultiverseCore.worldnew.config.WorldConfig;
import com.onarandombox.MultiverseCore.worldnew.config.WorldsConfigManager;
import com.onarandombox.MultiverseCore.worldnew.generators.GeneratorProvider;
import com.onarandombox.MultiverseCore.worldnew.helpers.DataStore.GameRulesStore;
import com.onarandombox.MultiverseCore.worldnew.helpers.FilesManipulator;
import com.onarandombox.MultiverseCore.worldnew.options.CreateWorldOptions;
import com.onarandombox.MultiverseCore.worldnew.options.ImportWorldOptions;
@ -39,12 +40,14 @@ import java.util.List;
import java.util.Map;
import static com.onarandombox.MultiverseCore.utils.message.MessageReplacement.replace;
import static com.onarandombox.MultiverseCore.worldnew.helpers.DataStore.*;
/**
* This manager contains all the world managing functions that your heart desires!
*/
@Service
public class WorldManager {
private static final List<String> CLONE_IGNORE_FILES = Arrays.asList("uid.dat", "session.dat");
private final Map<String, OfflineWorld> offlineWorldsMap;
private final Map<String, MVWorld> worldsMap;
@ -132,9 +135,7 @@ public class WorldManager {
if (isMVWorld(world) || !world.getAutoLoad()) {
return;
}
loadWorld(world).onFailure((failure) -> {
Logging.severe("Failed to load world %s: %s", world.getName(), failure);
});
loadWorld(world).onFailure((failure) -> Logging.severe("Failed to load world %s: %s", world.getName(), failure));
});
}
@ -461,6 +462,52 @@ public class WorldManager {
);
}
/**
* Clones an existing multiverse world.
*
* @param world The multiverse world to clone.
* @param newWorldName The name of the new world.
*/
public void cloneWorld(@NotNull MVWorld world, @NotNull String newWorldName) {
if (!worldNameChecker.isValidWorldName(newWorldName)) {
Logging.severe("Invalid world name: " + newWorldName);
return;
}
if (isOfflineWorld(newWorldName)) {
Logging.severe("World already exist offline: " + newWorldName);
return;
}
if (isMVWorld(newWorldName)) {
Logging.severe("World already loaded: " + newWorldName);
return;
}
if (worldNameChecker.isValidWorldFolder(newWorldName)) {
Logging.severe("World folder already exist: " + newWorldName);
return;
}
// TODO: Configure option on whether to copy these
GameRulesStore gameRulesStore = GameRulesStore.createAndCopyFrom(world);
WorldConfigStore worldConfigStore = WorldConfigStore.createAndCopyFrom(world);
File worldFolder = world.getBukkitWorld().map(World::getWorldFolder).getOrNull(); // TODO: Check null?
File newWorldFolder = new File(Bukkit.getWorldContainer(), newWorldName);
FileUtils.copyFolder(worldFolder, newWorldFolder, CLONE_IGNORE_FILES);
// TODO: Error handling
importWorld(ImportWorldOptions.worldName(newWorldName)
.environment(world.getEnvironment())
.generator(world.getGenerator())
);
// TODO: Error handling
getMVWorld(newWorldName).peek(newWorld -> {
gameRulesStore.pasteTo(newWorld);
worldConfigStore.pasteTo(newWorld);
saveWorldsConfig();
});
}
/**
* Creates a bukkit world.
*

View File

@ -2,6 +2,7 @@ package com.onarandombox.MultiverseCore.worldnew.config;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.configuration.handle.ConfigurationSectionHandle;
import com.onarandombox.MultiverseCore.configuration.node.NodeGroup;
import com.onarandombox.MultiverseCore.world.configuration.AllowedPortalType;
import com.onarandombox.MultiverseCore.worldnew.MVWorld;
import io.vavr.control.Try;
@ -14,6 +15,7 @@ import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
public class WorldConfig {
@ -41,6 +43,10 @@ public class WorldConfig {
return worldName;
}
public Collection<String> getConfigurablePropertyNames() {
return configNodes.getNodes().getNames();
}
public Try<Object> getProperty(String name) {
return configHandle.get(name);
}

View File

@ -0,0 +1,93 @@
package com.onarandombox.MultiverseCore.worldnew.helpers;
import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.worldnew.MVWorld;
import com.onarandombox.MultiverseCore.worldnew.OfflineWorld;
import org.bukkit.GameRule;
import org.bukkit.World;
import org.jvnet.hk2.annotations.Service;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Service
public interface DataStore<T> {
DataStore<T> copyFrom(T object);
DataStore<T> pasteTo(T object);
class GameRulesStore implements DataStore<MVWorld> {
public static GameRulesStore createAndCopyFrom(MVWorld world) {
return new GameRulesStore().copyFrom(world);
}
private Map<GameRule<?>, Object> gameRuleMap;
public GameRulesStore copyFrom(MVWorld world) {
this.gameRuleMap = new HashMap<>();
world.getBukkitWorld().peek(bukkitWorld -> {
Arrays.stream(GameRule.values()).forEach(gameRule -> {
Object value = bukkitWorld.getGameRuleValue(gameRule);
gameRuleMap.put(gameRule, value);
});
});
return this;
}
public GameRulesStore pasteTo(MVWorld world) {
if (gameRuleMap == null) {
return this;
}
world.getBukkitWorld().peek(bukkitWorld -> {
gameRuleMap.forEach((gameRule, value) -> {
if (!setGameRuleValue(bukkitWorld, gameRule, value)) {
Logging.warning("Failed to set game rule " + gameRule.getName() + " to " + value);
}
});
});
return this;
}
private <T> boolean setGameRuleValue(World world, GameRule<T> gameRule, Object value) {
try {
return world.setGameRule(gameRule, (T) value);
} catch (Exception e) {
Logging.fine(e.getMessage());
return false;
}
}
}
class WorldConfigStore implements DataStore<OfflineWorld> {
public static WorldConfigStore createAndCopyFrom(OfflineWorld world) {
return new WorldConfigStore().copyFrom(world);
}
private Map<String, Object> configMap;
@Override
public WorldConfigStore copyFrom(OfflineWorld world) {
this.configMap = new HashMap<>();
world.getConfigurablePropertyNames().forEach(name -> {
world.getProperty(name).peek(value -> configMap.put(name, value)).onFailure(e -> {
Logging.warning("Failed to get property " + name + " from world " + world.getName() + ": " + e.getMessage());
});
});
return this;
}
@Override
public WorldConfigStore pasteTo(OfflineWorld world) {
if (configMap == null) {
return this;
}
configMap.forEach((name, value) -> {
world.setProperty(name, value).onFailure(e -> {
Logging.warning("Failed to set property %s to %s for world %s: %s",
name, value, world.getName(), e.getMessage());
});
});
return this;
}
}
}