Compare commits

...

10 Commits

Author SHA1 Message Date
Ben Woo f189047264
Merge e8af526be5 into 278c51d056 2024-03-20 20:55:36 -06:00
Ben Woo 278c51d056
Merge pull request #3056 from Multiverse/zax71MV5/versionCommand
Add `/mv version`
2024-03-12 10:38:12 +08:00
Zax71 caed081f82 Update Ben's name 2024-03-08 18:03:42 +00:00
Zax71 7276da7665 Implement i18n 2024-02-24 13:48:57 +00:00
Zax71 90b8729129 Add Ben and Zax71 to authors 2024-02-24 13:37:23 +00:00
Zax71 c24f198ed0 Add `/mv version` 2024-02-24 13:36:13 +00:00
Ben Woo e8af526be5
Implement auto-complete for anchor names 2023-09-21 17:36:39 +08:00
Ben Woo 5337bc75cd
Implement anchor commands 2023-09-21 17:32:58 +08:00
Ben Woo 913ceb3006
Refactor anchor manager with varv and fix checkstyles 2023-09-21 17:32:47 +08:00
Ben Woo 65288f4037
Fix WorldManager#getWorld from bukkit World not mapping to worldname 2023-09-21 17:31:40 +08:00
11 changed files with 403 additions and 99 deletions

View File

@ -368,7 +368,7 @@ public class MultiverseCore extends JavaPlugin implements MVCore {
// TODO: Make this all Try<Void>
return configProvider.get().save().isSuccess()
&& worldManagerProvider.get().saveWorldsConfig()
&& anchorManagerProvider.get().saveAnchors();
&& anchorManagerProvider.get().saveAnchors().isSuccess();
}
/**

View File

@ -16,126 +16,126 @@ import java.util.Map;
import java.util.Set;
import com.dumptruckman.minecraft.util.Logging;
import io.vavr.control.Try;
import jakarta.inject.Inject;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.world.WorldManager;
import org.mvplugins.multiverse.core.world.entrycheck.WorldEntryCheckerProvider;
/**
* Manages anchors.
*/
@Service
public class AnchorManager {
private Map<String, Location> anchors;
private FileConfiguration anchorConfig;
private static final String ANCHORS_SECTION_NAME = "anchors";
private static final String ANCHORS_FILE_NAME = "anchors.yml";
private final Plugin plugin;
private final Map<String, Location> anchors;
private final File anchorsFile;
private final FileConfiguration anchorConfig;
private final LocationManipulation locationManipulation;
private final MVCoreConfig config;
private final WorldManager worldManager;
private final WorldEntryCheckerProvider worldEntryCheckerProvider;
@Inject
public AnchorManager(
MultiverseCore plugin,
LocationManipulation locationManipulation,
MVCoreConfig config
) {
this.plugin = plugin;
AnchorManager(
@NotNull MultiverseCore plugin,
@NotNull LocationManipulation locationManipulation,
@NotNull WorldManager worldManager,
@NotNull WorldEntryCheckerProvider worldEntryCheckerProvider) {
this.anchors = new HashMap<>();
this.anchorsFile = new File(plugin.getDataFolder(), ANCHORS_FILE_NAME);
this.anchorConfig = new YamlConfiguration();
this.locationManipulation = locationManipulation;
this.config = config;
this.anchors = new HashMap<String, Location>();
this.worldManager = worldManager;
this.worldEntryCheckerProvider = worldEntryCheckerProvider;
}
/**
* Loads all anchors.
*
* @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred.
*/
public void loadAnchors() {
this.anchors = new HashMap<String, Location>();
this.anchorConfig = YamlConfiguration.loadConfiguration(new File(this.plugin.getDataFolder(), "anchors.yml"));
this.ensureConfigIsPrepared();
ConfigurationSection anchorsSection = this.anchorConfig.getConfigurationSection("anchors");
Set<String> anchorKeys = anchorsSection.getKeys(false);
for (String key : anchorKeys) {
//world:x,y,z:pitch:yaw
Location anchorLocation = this.locationManipulation.stringToLocation(anchorsSection.getString(key, ""));
if (anchorLocation != null) {
Logging.config("Loading anchor: '%s'...", key);
public Try<Void> loadAnchors() {
return Try.run(() -> {
anchors.clear();
anchorConfig.load(anchorsFile);
ConfigurationSection anchorsSection = getAnchorConfigSection();
Set<String> anchorKeys = anchorsSection.getKeys(false);
anchorKeys.forEach(key -> {
//world:x,y,z:pitch:yaw
String locationString = anchorsSection.getString(key, "");
Location anchorLocation = this.locationManipulation.stringToLocation(locationString);
if (anchorLocation == null) {
Logging.warning("The location for anchor '%s' is INVALID.", key);
return;
}
this.anchors.put(key, anchorLocation);
} else {
Logging.warning("The location for anchor '%s' is INVALID.", key);
}
}
}
private void ensureConfigIsPrepared() {
if (this.anchorConfig.getConfigurationSection("anchors") == null) {
this.anchorConfig.createSection("anchors");
}
});
});
}
/**
* Saves all anchors.
*
* @return True if all anchors were successfully saved.
*/
public boolean saveAnchors() {
try {
this.anchorConfig.save(new File(this.plugin.getDataFolder(), "anchors.yml"));
return true;
} catch (IOException e) {
Logging.severe("Failed to save anchors.yml. Please check your file permissions.");
return false;
}
public Try<Void> saveAnchors() {
return Try.run(() -> anchorConfig.save(anchorsFile));
}
/**
* Gets the {@link Location} associated with an anchor.
* @param anchor The name of the anchor.
*
* @param anchorName The name of the anchor.
* @return The {@link Location}.
*/
public Location getAnchorLocation(String anchor) {
if (this.anchors.containsKey(anchor)) {
return this.anchors.get(anchor);
}
return null;
public Location getAnchorLocation(@Nullable String anchorName) {
return this.anchors.getOrDefault(anchorName, null);
}
/**
* Saves an anchor.
* @param anchor The name of the anchor.
* @param location The location of the anchor as string.
* @return True if the anchor was successfully saved.
*
* @param anchorName The name of the anchor.
* @param locationString The location of the anchor as string.
* @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred.
*/
public boolean saveAnchorLocation(String anchor, String location) {
Location parsed = this.locationManipulation.stringToLocation(location);
return parsed != null && this.saveAnchorLocation(anchor, parsed);
public Try<Location> saveAnchorLocation(String anchorName, String locationString) {
Location parsed = this.locationManipulation.stringToLocation(locationString);
if (parsed == null) {
return Try.failure(new IOException("Invalid location string: " + locationString));
}
return this.saveAnchorLocation(anchorName, parsed).map(ignore -> getAnchorLocation(anchorName));
}
/**
* Saves an anchor.
* @param anchor The name of the anchor.
* @param l The {@link Location} of the anchor.
* @return True if the anchor was successfully saved.
*
* @param anchorName The name of the anchor.
* @param location The {@link Location} of the anchor.
* @return Empty {@link Try} if all anchors were successfully loaded, or the exception if an error occurred.
*/
public boolean saveAnchorLocation(String anchor, Location l) {
if (l == null) {
return false;
}
this.anchorConfig.set("anchors." + anchor, this.locationManipulation.locationToString(l));
this.anchors.put(anchor, l);
return this.saveAnchors();
public Try<Void> saveAnchorLocation(@NotNull String anchorName, @NotNull Location location) {
getAnchorConfigSection().set(anchorName, this.locationManipulation.locationToString(location));
this.anchors.put(anchorName, location);
return saveAnchors();
}
/**
* Gets all anchors.
*
* @return An unmodifiable {@link Set} containing all anchors.
*/
public Set<String> getAllAnchors() {
@ -144,46 +144,61 @@ public class AnchorManager {
/**
* Gets all anchors that the specified {@link Player} can access.
* @param p The {@link Player}.
*
* @param player The {@link Player}.
* @return An unmodifiable {@link Set} containing all anchors the specified {@link Player} can access.
*/
public Set<String> getAnchors(Player p) {
if (p == null) {
return this.anchors.keySet();
public Set<String> getAnchors(@Nullable Player player) {
if (player == null) {
return getAllAnchors();
}
Set<String> myAnchors = new HashSet<String>();
for (String anchor : this.anchors.keySet()) {
Location ancLoc = this.anchors.get(anchor);
if (ancLoc == null) {
continue;
Set<String> myAnchors = new HashSet<>();
this.anchors.forEach((anchorName, anchorLocation) -> {
World anchorWorld = anchorLocation.getWorld();
if (anchorWorld == null) {
return;
}
String worldPerm = "multiverse.access." + ancLoc.getWorld().getName();
// Add to the list if we're not enforcing access
// OR
// We are enforcing access and the user has the permission.
if (!config.getEnforceAccess() ||
(config.getEnforceAccess() && p.hasPermission(worldPerm))) {
myAnchors.add(anchor);
} else {
Logging.finer(String.format("Not adding anchor %s to the list, user %s doesn't have the %s " +
"permission and 'enforceaccess' is enabled!",
anchor, p.getName(), worldPerm));
}
}
return Collections.unmodifiableSet(myAnchors);
worldManager.getWorld(anchorWorld)
.map(world -> worldEntryCheckerProvider.forSender(player).canAccessWorld(world).isSuccess())
.peek(success -> {
if (success) {
myAnchors.add(anchorName);
} else {
Logging.finer("Player '%s' cannot access anchor '%s'.",
player.getName(), anchorName);
}
})
.onEmpty(() -> {
Logging.finer("Anchor '%s' located in world '%s is not in a Multiverse world.",
anchorName, anchorLocation.getWorld().getName());
});
});
return myAnchors;
}
/**
* Deletes the specified anchor.
* @param s The name of the anchor.
*
* @param anchorName The name of the anchor.
* @return True if the anchor was successfully deleted.
*/
public boolean deleteAnchor(String s) {
if (this.anchors.containsKey(s)) {
this.anchors.remove(s);
this.anchorConfig.set("anchors." + s, null);
public Try<Void> deleteAnchor(String anchorName) {
if (this.anchors.containsKey(anchorName)) {
this.anchors.remove(anchorName);
getAnchorConfigSection().set(anchorName, null);
return this.saveAnchors();
}
return false;
return Try.failure(new Exception("Anchor " + anchorName + " not found."));
}
private ConfigurationSection getAnchorConfigSection() {
ConfigurationSection anchorsSection = anchorConfig.getConfigurationSection(ANCHORS_SECTION_NAME);
return anchorsSection == null
? anchorConfig.createSection(ANCHORS_SECTION_NAME)
: anchorsSection;
}
}

View File

@ -0,0 +1,45 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
@Service
@CommandAlias("mv")
class AnchorDeleteCommand extends MultiverseCommand {
private final AnchorManager anchorManager;
@Inject
AnchorDeleteCommand(@NotNull MVCommandManager commandManager, @NotNull AnchorManager anchorManager) {
super(commandManager);
this.anchorManager = anchorManager;
}
@Subcommand("anchor delete")
@CommandPermission("multiverse.core.anchor.delete")
@CommandCompletion("@anchornames")
@Syntax("<name>")
@Description("")
void onAnchorDeleteCommand(
MVCommandIssuer issuer,
@Syntax("<name>")
@Description("")
String anchorName) {
anchorManager.deleteAnchor(anchorName)
.onSuccess(ignore -> issuer.sendMessage("&aAnchor &f'" + anchorName + "&a deleted."))
.onFailure(e -> issuer.sendMessage("&cFailed to delete anchor. " + e.getMessage()));
}
}

View File

@ -0,0 +1,103 @@
package org.mvplugins.multiverse.core.commands;
import java.util.List;
import co.aikar.commands.InvalidCommandArgument;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Optional;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.commandtools.flags.CommandValueFlag;
import org.mvplugins.multiverse.core.commandtools.flags.ParsedCommandFlags;
import org.mvplugins.multiverse.core.display.ContentDisplay;
import org.mvplugins.multiverse.core.display.filters.ContentFilter;
import org.mvplugins.multiverse.core.display.filters.DefaultContentFilter;
import org.mvplugins.multiverse.core.display.filters.RegexContentFilter;
import org.mvplugins.multiverse.core.display.handlers.PagedSendHandler;
import org.mvplugins.multiverse.core.display.parsers.ListContentProvider;
@Service
@CommandAlias("mv")
class AnchorListCommand extends MultiverseCommand {
private final AnchorManager anchorManager;
private final LocationManipulation locationManipulation;
private final CommandValueFlag<Integer> PAGE_FLAG = flag(CommandValueFlag
.builder("--page", Integer.class)
.addAlias("-p")
.context(value -> {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new InvalidCommandArgument("Invalid page number: " + value);
}
})
.build());
private final CommandValueFlag<ContentFilter> FILTER_FLAG = flag(CommandValueFlag
.builder("--filter", ContentFilter.class)
.addAlias("-f")
.context(value -> {
try {
return RegexContentFilter.fromString(value);
} catch (IllegalArgumentException e) {
throw new InvalidCommandArgument("Invalid filter: " + value);
}
})
.build());
@Inject
AnchorListCommand(
@NotNull MVCommandManager commandManager,
@NotNull AnchorManager anchorManager,
@NotNull LocationManipulation locationManipulation) {
super(commandManager);
this.anchorManager = anchorManager;
this.locationManipulation = locationManipulation;
}
@Subcommand("anchor list")
@CommandPermission("multiverse.core.anchor.list")
@CommandCompletion("@flags:groupName=mvanchorlistcommand")
@Syntax("[--page <page>] [--filter <filter>]")
@Description("")
void onAnchorListCommand(
MVCommandIssuer issuer,
@Optional
@Syntax("[--page <page>] [--filter <filter>]")
@Description("")
String[] flags) {
ParsedCommandFlags parsedFlags = parseFlags(flags);
ContentDisplay.create()
.addContent(ListContentProvider.forContent(getAnchors(issuer.getPlayer())))
.withSendHandler(PagedSendHandler.create()
.withHeader("&3==== [ Multiverse Anchors ] ====")
.doPagination(true)
.withTargetPage(parsedFlags.flagValue(PAGE_FLAG, 1))
.withFilter(parsedFlags.flagValue(FILTER_FLAG, DefaultContentFilter.get())))
.send(issuer);
}
private List<String> getAnchors(Player player) {
return anchorManager.getAnchors(player).stream().map(anchorName -> {
Location anchorLocation = anchorManager.getAnchorLocation(anchorName);
return "&a" + anchorName + "&7 - &f" + locationManipulation.locationToString(anchorLocation);
}).toList();
}
}

View File

@ -0,0 +1,84 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandCompletion;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Flags;
import co.aikar.commands.annotation.Single;
import co.aikar.commands.annotation.Subcommand;
import co.aikar.commands.annotation.Syntax;
import jakarta.inject.Inject;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.api.LocationManipulation;
import org.mvplugins.multiverse.core.commandtools.MVCommandIssuer;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
@Service
@CommandAlias("mv")
class AnchorSetCommand extends MultiverseCommand {
private final AnchorManager anchorManager;
private final LocationManipulation locationManipulation;
@Inject
AnchorSetCommand(
@NotNull MVCommandManager commandManager,
@NotNull AnchorManager anchorManager,
@NotNull LocationManipulation locationManipulation) {
super(commandManager);
this.anchorManager = anchorManager;
this.locationManipulation = locationManipulation;
}
@Subcommand("anchor set")
@CommandPermission("multiverse.core.anchor.create")
@CommandCompletion("@anchornames")
@Syntax("<name> [location]")
@Description("")
void onAnchorSetCommand(
MVCommandIssuer issuer,
@Flags("resolve=issuerOnly")
Player player,
@Single
@Syntax("<name>")
@Description("")
//TODO: Check if anchor name is valid or exists already.
String anchorName) {
Location anchorLocation = player.getLocation();
anchorManager.saveAnchorLocation(anchorName, anchorLocation)
.onSuccess(ignore -> issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f"
+ locationManipulation.locationToString(anchorLocation)))
.onFailure(e -> issuer.sendMessage("&cFailed to set anchor &f" + anchorName + "&c." + e.getMessage()));
}
@Subcommand("anchor set")
@CommandPermission("multiverse.core.anchor.create")
@CommandCompletion("@anchornames @locations")
@Syntax("<name> [location]")
@Description("")
void onAnchorSetCommand(
MVCommandIssuer issuer,
@Syntax("<name>")
@Description("")
String anchorName,
@Single
@Syntax("[location]")
@Description("")
String locationString) {
anchorManager.saveAnchorLocation(anchorName, locationString)
.onSuccess(anchorLocation -> issuer.sendMessage("&aAnchor &f" + anchorName + "&a set to &f"
+ locationManipulation.locationToString(anchorLocation)))
.onFailure(e -> issuer.sendMessage("&cFailed to set anchor '" + anchorName + "'&c. " + e.getMessage()));
}
}

View File

@ -0,0 +1,37 @@
package org.mvplugins.multiverse.core.commands;
import co.aikar.commands.BukkitCommandIssuer;
import co.aikar.commands.MessageType;
import co.aikar.commands.annotation.CommandAlias;
import co.aikar.commands.annotation.CommandPermission;
import co.aikar.commands.annotation.Description;
import co.aikar.commands.annotation.Subcommand;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.MultiverseCore;
import org.mvplugins.multiverse.core.commandtools.MVCommandManager;
import org.mvplugins.multiverse.core.commandtools.MultiverseCommand;
import org.mvplugins.multiverse.core.utils.MVCorei18n;
@Service
@CommandAlias("mv")
class VersionCommand extends MultiverseCommand {
private final MultiverseCore plugin;
@Inject
VersionCommand(@NotNull MVCommandManager commandManager, MultiverseCore plugin) {
super(commandManager);
this.plugin = plugin;
}
@Subcommand("version")
@CommandPermission("multiverse.core.version")
@Description("{@@mv-core.version.description}")
void versionCommand(BukkitCommandIssuer issuer) {
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_MV, "{version}", plugin.getDescription().getVersion());
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_AUTHORS, "{authors}", String.join(", ", plugin.getDescription().getAuthors()));
issuer.sendMessage(MessageType.INFO, MVCorei18n.VERSION_SECRETCODE); // An in joke I don't get...
}
}

View File

@ -26,6 +26,7 @@ import org.bukkit.World;
import org.jetbrains.annotations.NotNull;
import org.jvnet.hk2.annotations.Service;
import org.mvplugins.multiverse.core.anchor.AnchorManager;
import org.mvplugins.multiverse.core.config.MVCoreConfig;
import org.mvplugins.multiverse.core.configuration.handle.PropertyModifyAction;
import org.mvplugins.multiverse.core.destination.DestinationsProvider;
@ -39,6 +40,7 @@ class MVCommandCompletions extends PaperCommandCompletions {
private final MVCommandManager commandManager;
private final WorldManager worldManager;
private final AnchorManager anchorManager;
private final DestinationsProvider destinationsProvider;
private final MVCoreConfig config;
@ -46,14 +48,17 @@ class MVCommandCompletions extends PaperCommandCompletions {
MVCommandCompletions(
@NotNull MVCommandManager mvCommandManager,
@NotNull WorldManager worldManager,
@NotNull AnchorManager anchorManager,
@NotNull DestinationsProvider destinationsProvider,
@NotNull MVCoreConfig config) {
super(mvCommandManager);
this.commandManager = mvCommandManager;
this.worldManager = worldManager;
this.anchorManager = anchorManager;
this.destinationsProvider = destinationsProvider;
this.config = config;
registerAsyncCompletion("anchornames", this::suggestAnchorNames);
registerAsyncCompletion("commands", this::suggestCommands);
registerAsyncCompletion("destinations", this::suggestDestinations);
registerStaticCompletion("difficulties", suggestEnums(Difficulty.class));
@ -77,6 +82,10 @@ class MVCommandCompletions extends PaperCommandCompletions {
setDefaultCompletion("mvworlds", LoadedMultiverseWorld.class);
}
private Collection<String> suggestAnchorNames(BukkitCommandCompletionContext context) {
return anchorManager.getAnchors(context.getPlayer());
}
private Collection<String> suggestCommands(BukkitCommandCompletionContext context) {
String rootCmdName = context.getConfig();
if (rootCmdName == null) {

View File

@ -101,6 +101,11 @@ public enum MVCorei18n implements MessageKeyProvider {
UNLOAD_UNLOADING,
UNLOAD_SUCCESS,
// version command
VERSION_MV,
VERSION_AUTHORS,
VERSION_SECRETCODE,
// debug command
DEBUG_INFO_OFF,
DEBUG_INFO_ON,

View File

@ -734,7 +734,7 @@ public class WorldManager {
* @return The world if it exists.
*/
public Option<MultiverseWorld> getWorld(@Nullable World world) {
return Option.of(world).flatMap(this::getWorld);
return Option.of(world).map(World::getName).flatMap(this::getWorld);
}
/**

View File

@ -139,6 +139,12 @@ mv-core.unload.success=&aWorld '{world}' unloaded!
# /mv usage
mv-core.usage.description=Show Multiverse-Core command usage.
# /mv version
mv-core.version.description=Displays version and authors
mv-core.version.mv=Multiverse Core Version &fv{version}
mv-core.version.authors=Multiverse Core Authors &f{authors}
mv-core.version.secretcode=Special Code: &fFRN002
# commands error
mv-core.commands.error.playersonly=&cThis command can only be used by players
mv-core.commands.error.multiverseworldonly=&cThis can only be used in multiverse worlds

View File

@ -1,6 +1,6 @@
name: Multiverse-Core
main: org.mvplugins.multiverse.core.MultiverseCore
authors: ['dumptruckman', 'Rigby', 'fernferret', 'lithium3141', 'main--']
authors: ['dumptruckman', 'Rigby', 'fernferret', 'lithium3141', 'main--', 'benwoo1110', 'Zax71']
website: 'https://dev.bukkit.org/projects/multiverse-core'
softdepend: ['Vault', 'PlaceholderAPI']
api-version: 1.13