Compare commits

..
Author SHA1 Message Date
benwoo1110 92951739fb Remove ability to unload default worlds. 2021-03-18 23:32:47 +08:00
Ben Woo e843b0711d Merge pull request #2590 from Multiverse/small-wm-updates
Small WorldManager updates
2021-03-10 06:41:18 +08:00
Ben Woo eddfaf7df0 Merge pull request #2593 from Multiverse/destination-id
Add method to get registered destination identifiers.
2021-03-10 06:39:31 +08:00
nicegamer7 d1150ab5f8 Merge pull request #2591 from Multiverse/uuid-regex
Make UUID regex less restrictive.
2021-03-09 09:46:33 -05:00
benwoo1110 1965aecfba Add method to get registered destination identifiers. 2021-03-09 20:05:23 +08:00
benwoo1110 ce34ae44d8 Make UUID regex less restrictive. 2021-03-09 11:48:26 +08:00
benwoo1110 f5a2d7bc47 Add getPotentialWorlds method to WorldManager. 2021-03-09 10:40:46 +08:00
benwoo1110 24295025ef Add option to lookup mvworlds without checking for aliases. 2021-03-09 10:35:42 +08:00
Ben Woo e3f56f6498 Merge pull request #2586 from Multiverse/reflect-helper
Add ReflectHelper class to better facilitate various reflection actions.
2021-03-09 01:18:29 +08:00
Ben Woo bd0c3cff3f Merge pull request #2587 from Multiverse/player-desination
Player destination
2021-03-09 00:32:20 +08:00
Ben Woo 7c4ab035a0 Merge pull request #2588 from Multiverse/world-name-check
Implement new WorldNameChecker
2021-03-09 00:31:58 +08:00
Ben Woo 2593080f77 Merge pull request #2567 from Multiverse/player-utils
Add support to find players based on name, UUID or selectors.
2021-03-09 00:29:22 +08:00
benwoo1110 0d4595d511 Implement new WorldNameChecker for better preliminary checks of world name. 2021-03-08 18:03:35 +08:00
benwoo1110 bf820c2c27 Add getPlayerAwareDestination to DestinationFactory. 2021-03-08 17:09:03 +08:00
benwoo1110 a12718d354 Add ReflectHelper class to better facilitate various reflection actions. 2021-03-08 16:25:33 +08:00
Ben Woo 7c59dcbcb9 Merge pull request #2494 from benwoo1110/property-fix
Fix cannot remove block currency with mvm set command.
2021-03-04 15:32:34 +08:00
Ben Woo 15cacba7c9 Merge pull request #2573 from Multiverse/java-11
Fix compiling with Java 11+
2021-03-02 09:01:38 +08:00
Ben Woo adbe88daa6 Merge pull request #2565 from Multiverse/fis-respawn-anchor
Add respawn anchor support.
2021-03-02 00:36:43 +08:00
Kermina Awad 5350c012f7 fix compiling on java 15 2021-02-25 17:48:17 -05:00
benwoo1110 a91f377cf5 Fix compile on java 11. 2021-02-24 16:40:21 +08:00
Ben Woo c3ae5ac210 Merge pull request #2539 from Multiverse/disable_buscript
Add a Config Option to Disable Buscript
2021-02-24 14:12:35 +08:00
Ben Woo f4ce740c06 Merge pull request #2568 from Multiverse/fullname-face-direction
Add support for fullname face direction.
2021-02-24 14:09:56 +08:00
Kermina Awad 3ff8d34053 add config option to disable buscript 2021-02-23 14:38:54 -05:00
benwoo1110 f362f09380 Add support for fullname face direction.
So we can do `w:world:north` on top of the current ``w:world:n`.
2021-02-21 17:19:18 +08:00
benwoo1110 62b789dff5 Add support to find players based on name, UUID or selectors. 2021-02-21 16:47:13 +08:00
benwoo1110 84a9db597a Add respawn anchor support. 2021-02-19 00:57:19 +08:00
benwoo1110 48601e4215 Fix cannot remove block currency with mvm set command.
Setting currency to 0 is now considered as no currency value.
2020-11-27 21:14:27 +08:00
22 changed files with 845 additions and 113 deletions

No files matched your search

+12 -6
View File
@@ -215,7 +215,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.1</version>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
@@ -264,13 +264,19 @@
<plugin>
<groupId>se.eris</groupId>
<artifactId>notnull-instrumenter-maven-plugin</artifactId>
<version>0.6.8</version>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>instrument</goal>
<goal>tests-instrument</goal>
</goals>
<configuration>
<notNull>
<param>org.jetbrains.annotations.NotNull</param>
<param>javax.validation.constraints.NotNull</param>
</notNull>
</configuration>
</execution>
</executions>
</plugin>
@@ -335,7 +341,7 @@
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>20.1.0</version>
<version>16.0.2</version>
</dependency>
<dependency>
<groupId>de.themoep.idconverter</groupId>
@@ -353,21 +359,21 @@
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.0</version>
<version>2.0.9</version>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>2.0.0</version>
<version>2.0.9</version>
<type>jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.0</version>
<version>2.0.9</version>
<type>jar</type>
<scope>test</scope>
</dependency>
@@ -88,6 +88,7 @@ import com.onarandombox.MultiverseCore.listeners.MVWeatherListener;
import com.onarandombox.MultiverseCore.listeners.MVWorldInitListener;
import com.onarandombox.MultiverseCore.listeners.MVWorldListener;
import com.onarandombox.MultiverseCore.utils.AnchorManager;
import com.onarandombox.MultiverseCore.utils.CompatibilityLayer;
import com.onarandombox.MultiverseCore.utils.MVEconomist;
import com.onarandombox.MultiverseCore.utils.MVMessaging;
import com.onarandombox.MultiverseCore.utils.MVPermissions;
@@ -254,6 +255,8 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core {
// Setup our SafeTTeleporter
this.safeTTeleporter = new SimpleSafeTTeleporter(this);
this.unsafeCallWrapper = new UnsafeCallWrapper(this);
// Setup our CompatibilityLayer
CompatibilityLayer.init();
}
@@ -353,13 +356,16 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core {
* Initializes the buscript javascript library.
*/
private void initializeBuscript() {
try {
buscript = new Buscript(this);
// Add global variable "multiverse" to javascript environment
buscript.setScriptVariable("multiverse", this);
} catch (NullPointerException e) {
buscript = null;
Logging.warning("Buscript failed to load! The script command will be disabled!");
buscript = null;
if (this.getMVConfig().getEnableBuscript()) {
try {
buscript = new Buscript(this);
// Add global variable "multiverse" to javascript environment
buscript.setScriptVariable("multiverse", this);
} catch (NullPointerException e) {
Logging.warning("Buscript failed to load! The script command will be disabled!");
}
}
}
@@ -56,6 +56,8 @@ public class MultiverseCoreConfiguration extends SerializationConfig implements
@Property
private volatile boolean displaypermerrors;
@Property
private volatile boolean enablebuscript;
@Property
private volatile int globaldebug;
@Property
private volatile boolean silentstart;
@@ -99,6 +101,7 @@ public class MultiverseCoreConfiguration extends SerializationConfig implements
teleportintercept = true;
firstspawnoverride = true;
displaypermerrors = true;
enablebuscript = true;
globaldebug = 0;
messagecooldown = 5000;
teleportcooldown = 1000;
@@ -213,6 +216,22 @@ public class MultiverseCoreConfiguration extends SerializationConfig implements
return this.displaypermerrors;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getEnableBuscript() {
return this.enablebuscript;
}
/**
* {@inheritDoc}
*/
@Override
public void setEnableBuscript(boolean enableBuscript) {
this.enablebuscript = enableBuscript;
}
/**
* {@inheritDoc}
*/
@@ -174,6 +174,16 @@ public interface MVWorldManager {
*/
MultiverseWorld getMVWorld(String name);
/**
* Returns a {@link MultiverseWorld} if the world with name given exists, and null if it does not.
* This will search optionally for alias names.
*
* @param name The name or optionally the alias of the world to get.
* @param checkAliases Indicates whether to check for world alias name.
* @return A {@link MultiverseWorld} or null.
*/
MultiverseWorld getMVWorld(String name, boolean checkAliases);
/**
* Returns a {@link MultiverseWorld} if it exists, and null if it does not.
*
@@ -183,13 +193,24 @@ public interface MVWorldManager {
MultiverseWorld getMVWorld(World world);
/**
* Checks to see if the given name is a valid {@link MultiverseWorld}.
* Checks to see if the given name is a valid {@link MultiverseWorld}
* Searches based on world name AND alias.
*
* @param name The name or alias of the world to check.
* @return True if the world exists, false if not.
*/
boolean isMVWorld(String name);
/**
* Checks to see if the given name is a valid {@link MultiverseWorld}.
* Optionally searches by alias is specified.
*
* @param name The name or alias of the world to check.
* @param checkAliases Indicates whether to check for world alias name.
* @return True if the world exists, false if not.
*/
boolean isMVWorld(String name, boolean checkAliases);
/**
* Checks to see if the given world is a valid {@link MultiverseWorld}.
*
@@ -198,6 +219,38 @@ public interface MVWorldManager {
*/
boolean isMVWorld(World world);
/**
* Checks if a world name is the servers default world.
*
* Generally, default world is defined in level-name of server properties file along with
* it's 2 dimensions: [world]_nether, [world]_the_end.
*
* @param world The world to check.
* @return True if world name is a default world, else false.
*/
boolean isDefaultWorld(MultiverseWorld world);
/**
* Checks if a world name is the servers default world.
*
* Generally, default world is defined in level-name of server properties file along with
* it's 2 dimensions: [world]_nether, [world]_the_end.
*
* @param worldName The name of the world to check.
* @return True if world name is a default world, else false.
*/
boolean isDefaultWorld(String worldName);
/**
* Gets all the default world names of the server.
*
* Generally, default world is defined in level-name of server properties file along with
* it's 2 dimensions: [world]_nether, [world]_the_end.
*
* @return Collection of default world names.
*/
Collection<String> getDefaultWorldNames();
/**
* Load the Worlds &amp; Settings from the configuration file.
*
@@ -314,4 +367,11 @@ public interface MVWorldManager {
* does not exist. {@code includeLoaded} if the world exists and is loaded.
*/
boolean hasUnloadedWorld(String name, boolean includeLoaded);
/**
* Get all the possible worlds that Multiverse has detected to be importable.
*
* @return A collection of world names that are deemed importable.
*/
Collection<String> getPotentialWorlds();
}
@@ -86,6 +86,18 @@ public interface MultiverseCoreConfig extends ConfigurationSerializable {
*/
boolean getDisplayPermErrors();
/**
* Sets enableBuscript.
* @param enableBuscript The new value.
*/
void setEnableBuscript(boolean enableBuscript);
/**
* Gets enableBuscript.
* @return enableBuscript.
*/
boolean getEnableBuscript();
/**
* Sets firstSpawnOverride.
* @param firstSpawnOverride The new value.
@@ -11,6 +11,7 @@ import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.destination.InvalidDestination;
import com.onarandombox.MultiverseCore.utils.MVPermissions;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -37,7 +38,7 @@ public class CheckCommand extends MultiverseCommand {
@Override
public void runCommand(CommandSender sender, List<String> args) {
Player p = this.plugin.getServer().getPlayerExact(args.get(0));
Player p = PlayerFinder.get(args.get(0), sender);
if (p == null) {
sender.sendMessage("Could not find player " + ChatColor.GREEN + args.get(0));
sender.sendMessage("Are they online?");
@@ -9,7 +9,7 @@ package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.utils.WorldNameChecker;
import com.pneumaticraft.commandhandler.CommandHandler;
import org.bukkit.ChatColor;
import org.bukkit.World.Environment;
@@ -18,8 +18,6 @@ import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -45,55 +43,17 @@ public class ImportCommand extends MultiverseCommand {
this.worldManager = this.plugin.getMVWorldManager();
}
/**
* A very basic check to see if a folder has a level.dat file.
* If it does, we can safely assume it's a world folder.
*
* @param worldFolder The File that may be a world.
* @return True if it looks like a world, false if not.
*/
private static boolean checkIfIsWorld(File worldFolder) {
if (worldFolder.isDirectory()) {
File[] files = worldFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.toLowerCase().endsWith(".dat");
}
});
if (files != null && files.length > 0) {
return true;
}
}
return false;
}
private String getPotentialWorlds() {
File worldFolder = this.plugin.getServer().getWorldContainer();
if (worldFolder == null) {
return "";
}
File[] files = worldFolder.listFiles();
String worldList = "";
Collection<MultiverseWorld> worlds = this.worldManager.getMVWorlds();
List<String> worldStrings = new ArrayList<String>();
for (MultiverseWorld world : worlds) {
worldStrings.add(world.getName());
}
for (String world : this.worldManager.getUnloadedWorlds()) {
worldStrings.add(world);
}
private String getPotentialWorldStrings() {
final Collection<String> potentialWorlds = this.worldManager.getPotentialWorlds();
StringBuilder worldList = new StringBuilder();
ChatColor currColor = ChatColor.WHITE;
for (File file : files) {
if (file.isDirectory() && checkIfIsWorld(file) && !worldStrings.contains(file.getName())) {
worldList += currColor + file.getName() + " ";
if (currColor == ChatColor.WHITE) {
currColor = ChatColor.YELLOW;
} else {
currColor = ChatColor.WHITE;
}
}
for (String world : potentialWorlds) {
worldList.append(currColor).append(world).append(' ');
currColor = currColor == ChatColor.WHITE ? ChatColor.YELLOW : ChatColor.WHITE;
}
return worldList;
return worldList.toString();
}
private String trimWorldName(String userInput) {
@@ -106,7 +66,7 @@ public class ImportCommand extends MultiverseCommand {
String worldName = trimWorldName(args.get(0));
if (worldName.toLowerCase().equals("--list") || worldName.toLowerCase().equals("-l")) {
String worldList = this.getPotentialWorlds();
String worldList = this.getPotentialWorldStrings();
if (worldList.length() > 2) {
sender.sendMessage(ChatColor.AQUA + "====[ These look like worlds ]====");
sender.sendMessage(worldList);
@@ -149,10 +109,10 @@ public class ImportCommand extends MultiverseCommand {
if (!worldFile.exists()) {
sender.sendMessage(ChatColor.RED + "FAILED.");
String worldList = this.getPotentialWorlds();
String worldList = this.getPotentialWorldStrings();
sender.sendMessage("That world folder does not exist. These look like worlds to me:");
sender.sendMessage(worldList);
} else if (!checkIfIsWorld(worldFile)) {
} else if (!WorldNameChecker.isValidWorldFolder(worldFile)) {
sender.sendMessage(ChatColor.RED + "FAILED.");
sender.sendMessage(String.format("'%s' does not appear to be a world. It is lacking a .dat file.",
worldName));
@@ -9,6 +9,7 @@ package com.onarandombox.MultiverseCore.commands;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
@@ -50,7 +51,7 @@ public class SpawnCommand extends MultiverseCommand {
sender.sendMessage("You don't have permission to teleport another player to spawn. (multiverse.core.spawn.other)");
return;
}
Player target = this.plugin.getServer().getPlayerExact(args.get(0));
Player target = PlayerFinder.get(args.get(0), sender);
if (target != null) {
target.sendMessage("Teleporting to this world's spawn...");
spawnAccurately(target);
@@ -18,6 +18,7 @@ import com.onarandombox.MultiverseCore.destination.WorldDestination;
import com.onarandombox.MultiverseCore.enums.TeleportResult;
import com.onarandombox.MultiverseCore.event.MVTeleportEvent;
import com.onarandombox.MultiverseCore.api.SafeTTeleporter;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
@@ -28,7 +29,6 @@ import org.bukkit.permissions.PermissionDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
/**
* Used to teleport players.
@@ -59,7 +59,7 @@ public class TeleportCommand extends MultiverseCommand {
String destinationName;
if (args.size() == 2) {
teleportee = this.plugin.getServer().getPlayerExact(args.get(0));
teleportee = PlayerFinder.get(args.get(0), sender);
if (teleportee == null) {
this.messaging.sendMessage(sender, String.format("Sorry, I couldn't find player: %s%s",
ChatColor.GOLD, args.get(0)), false);
@@ -75,22 +75,9 @@ public class TeleportCommand extends MultiverseCommand {
}
teleportee = (Player) sender;
}
// Special case for cannons:
if (destinationName.matches("(?i)cannon-[\\d]+(\\.[\\d]+)?")) {
String[] cannonSpeed = destinationName.split("-");
try {
double speed = Double.parseDouble(cannonSpeed[1]);
destinationName = "ca:" + teleportee.getWorld().getName() + ":" + teleportee.getLocation().getX()
+ "," + teleportee.getLocation().getY() + "," + teleportee.getLocation().getZ() + ":"
+ teleportee.getLocation().getPitch() + ":" + teleportee.getLocation().getYaw() + ":" + speed;
} catch (Exception e) {
destinationName = "i:invalid";
}
}
DestinationFactory df = this.plugin.getDestFactory();
MVDestination d = df.getDestination(destinationName);
MVDestination d = df.getPlayerAwareDestination(teleportee, destinationName);
MVTeleportEvent teleportEvent = new MVTeleportEvent(d, teleportee, teleporter, true);
this.plugin.getServer().getPluginManager().callEvent(teleportEvent);
@@ -64,6 +64,7 @@ public class VersionCommand extends MultiverseCommand {
+ "[Multiverse-Core] teleportintercept: " + plugin.getMVConfig().getTeleportIntercept() + '\n'
+ "[Multiverse-Core] firstspawnoverride: " + plugin.getMVConfig().getFirstSpawnOverride() + '\n'
+ "[Multiverse-Core] displaypermerrors: " + plugin.getMVConfig().getDisplayPermErrors() + '\n'
+ "[Multiverse-Core] enablebuscript: " + plugin.getMVConfig().getEnableBuscript() + '\n'
+ "[Multiverse-Core] globaldebug: " + plugin.getMVConfig().getGlobalDebug() + '\n'
+ "[Multiverse-Core] silentstart: " + plugin.getMVConfig().getSilentStart() + '\n'
+ "[Multiverse-Core] messagecooldown: " + plugin.getMessaging().getCooldown() + '\n'
@@ -22,6 +22,8 @@ public class EntryFee extends SerializationConfig {
@Nullable
private Material currency;
private final Material DISABLED_MATERIAL = Material.AIR;
public EntryFee() {
super();
}
@@ -51,6 +53,9 @@ public class EntryFee extends SerializationConfig {
*/
@Nullable
public Material getCurrency() {
if (currency == null || currency.equals(DISABLED_MATERIAL)) {
return null;
}
return currency;
}
@@ -11,16 +11,27 @@ import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVDestination;
import com.onarandombox.MultiverseCore.commands.TeleportCommand;
import com.onarandombox.MultiverseCore.utils.PermissionTools;
import com.onarandombox.MultiverseCore.utils.PlayerFinder;
import com.pneumaticraft.commandhandler.Command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/** A factory class that will create destinations from specific strings. */
public class DestinationFactory {
private static final Pattern CANNON_PATTERN = Pattern.compile("(?i)cannon-[\\d]+(\\.[\\d]+)?");
private MultiverseCore plugin;
private Map<String, Class<? extends MVDestination>> destList;
private Command teleportCommand;
@@ -36,6 +47,59 @@ public class DestinationFactory {
}
}
/**
* Parse a destination that has relation to sender, such as a cannon or player destination.
*
* @param teleportee The player that is going to be teleported.
* @param destinationName The destination to parse.
* @return A non-null MVDestination
*/
@NotNull
public MVDestination getPlayerAwareDestination(@NotNull Player teleportee,
@NotNull String destinationName) {
// Prioritise world, in the event that a world is named after a player online.
if (Bukkit.getWorld(destinationName) != null) {
return getDestination(destinationName);
}
Player targetPlayer = PlayerFinder.get(destinationName, teleportee);
if (targetPlayer != null) {
return getDestination("pl:" + targetPlayer.getName());
}
if (CANNON_PATTERN.matcher(destinationName).matches()) {
return getDestination(parseCannonDest(teleportee, destinationName));
}
return getDestination(destinationName);
}
/**
* Parses a cannon destination.
*
* @param teleportee The player that is going to be teleported.
* @param destinationName The destination to parse.
* @return A destination string.
*/
@NotNull
private String parseCannonDest(@NotNull Player teleportee,
@NotNull String destinationName) {
String[] cannonSpeed = destinationName.split("-");
try {
double speed = Double.parseDouble(cannonSpeed[1]);
destinationName = "ca:" + teleportee.getWorld().getName() + ":" + teleportee.getLocation().getX()
+ "," + teleportee.getLocation().getY() + "," + teleportee.getLocation().getZ() + ":"
+ teleportee.getLocation().getPitch() + ":" + teleportee.getLocation().getYaw() + ":" + speed;
}
catch (Exception e) {
destinationName = "i:invalid";
}
return destinationName;
}
/**
* Gets a new destination from a string.
* Returns a new InvalidDestination if the string could not be parsed.
@@ -101,4 +165,8 @@ public class DestinationFactory {
this.teleportCommand.addAdditonalPermission(other);
return true;
}
public Collection<String> getRegisteredIdentifiers() {
return this.destList.keySet();
}
}
@@ -0,0 +1,7 @@
package com.onarandombox.MultiverseCore.enums;
public enum RespawnType {
BED,
ANCHOR,
OTHER
}
@@ -15,7 +15,9 @@ import com.dumptruckman.minecraft.util.Logging;
import com.onarandombox.MultiverseCore.MultiverseCore;
import com.onarandombox.MultiverseCore.api.MVWorldManager;
import com.onarandombox.MultiverseCore.api.MultiverseWorld;
import com.onarandombox.MultiverseCore.enums.RespawnType;
import com.onarandombox.MultiverseCore.event.MVRespawnEvent;
import com.onarandombox.MultiverseCore.utils.CompatibilityLayer;
import com.onarandombox.MultiverseCore.utils.PermissionTools;
import org.bukkit.GameMode;
import org.bukkit.Location;
@@ -69,9 +71,16 @@ public class MVPlayerListener implements Listener {
return;
}
RespawnType respawnType = RespawnType.OTHER;
if (event.isBedSpawn()) {
respawnType = RespawnType.BED;
}
if (CompatibilityLayer.isAnchorSpawn(event)) {
respawnType = RespawnType.ANCHOR;
}
if (mvWorld.getBedRespawn() && event.isBedSpawn()) {
Logging.fine("Spawning " + event.getPlayer().getName() + " at their bed");
if (mvWorld.getBedRespawn() && (respawnType == RespawnType.BED || respawnType == RespawnType.ANCHOR)) {
Logging.fine("Spawning %s at their %s", event.getPlayer().getName(), respawnType);
return;
}
@@ -0,0 +1,46 @@
package com.onarandombox.MultiverseCore.utils;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.Bukkit;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.lang.reflect.Method;
/**
* A utility class to enable version specific minecraft features.
*/
public class CompatibilityLayer {
private static Method checkAnchorSpawn;
/**
* Initialise the reflection methods.
*/
public static void init() {
try {
checkAnchorSpawn = PlayerRespawnEvent.class.getDeclaredMethod("isAnchorSpawn");
} catch (NoSuchMethodException e) {
Logging.fine("%s does not support respawn anchors.", Bukkit.getVersion());
}
}
/**
* Check if the respawn point is of respawn anchor type.
* Introduced in minecraft 1.16
*
* @param event A player respawn event.
* @return If the respawn location is an anchor point.
*/
public static boolean isAnchorSpawn(PlayerRespawnEvent event) {
if (checkAnchorSpawn == null) {
return false;
}
try {
return (boolean) checkAnchorSpawn.invoke(event);
} catch (Exception e) {
Logging.warning("Error checking for: %s", checkAnchorSpawn);
e.printStackTrace();
}
return false;
}
}
@@ -0,0 +1,176 @@
package com.onarandombox.MultiverseCore.utils;
import com.dumptruckman.minecraft.util.Logging;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Helper class to get {@link Player} from name, UUID or Selectors.
*/
public class PlayerFinder {
private static final Pattern UUID_REGEX = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
private static final Pattern COMMA_SPLIT = Pattern.compile(",");
/**
* Get a {@link Player} based on an identifier of name UUID or selector.
*
* @param playerIdentifier An identifier of name UUID or selector.
* @param sender Target sender for selector.
* @return The player if found, else null.
*/
@Nullable
public static Player get(@NotNull String playerIdentifier,
@NotNull CommandSender sender) {
Player targetPlayer = getByName(playerIdentifier);
if (targetPlayer != null) {
return targetPlayer;
}
targetPlayer = getByUuid(playerIdentifier);
if (targetPlayer != null) {
return targetPlayer;
}
return getBySelector(playerIdentifier, sender);
}
/**
* Get multiple {@link Player} based on many identifiers of name UUID or selector.
*
* @param playerIdentifiers An identifier of multiple names, UUIDs or selectors, separated by comma.
* @param sender Target sender for selector.
* @return A list of all the {@link Player} found.
*/
@Nullable
public static List<Player> getMulti(@NotNull String playerIdentifiers,
@NotNull CommandSender sender) {
String[] playerIdentifierArray = COMMA_SPLIT.split(playerIdentifiers);
if (playerIdentifierArray == null || playerIdentifierArray.length == 0) {
return null;
}
List<Player> playerResults = new ArrayList<>();
for (String playerIdentifier : playerIdentifierArray) {
Player targetPlayer = getByName(playerIdentifier);
if (targetPlayer != null) {
playerResults.add(targetPlayer);
continue;
}
targetPlayer = getByUuid(playerIdentifier);
if (targetPlayer != null) {
playerResults.add(targetPlayer);
continue;
}
List<Player> targetPlayers = getMultiBySelector(playerIdentifier, sender);
if (targetPlayers != null) {
playerResults.addAll(targetPlayers);
}
}
return playerResults;
}
/**
* Get a {@link Player} based on player name.
*
* @param playerName Name of a {@link Player}.
* @return The player if found, else null.
*/
@Nullable
public static Player getByName(@NotNull String playerName) {
return Bukkit.getPlayerExact(playerName);
}
/**
* Get a {@link Player} based on player UUID.
*
* @param playerUuid UUID of a player.
* @return The player if found, else null.
*/
@Nullable
public static Player getByUuid(@NotNull String playerUuid) {
if (!UUID_REGEX.matcher(playerUuid).matches()) {
return null;
}
UUID uuid;
try {
uuid = UUID.fromString(playerUuid);
} catch (Exception e) {
return null;
}
return getByUuid(uuid);
}
/**
* Get a {@link Player} based on playerUUID.
*
* @param playerUuid UUID of a player.
* @return The player if found, else null.
*/
@Nullable
public static Player getByUuid(@NotNull UUID playerUuid) {
return Bukkit.getPlayer(playerUuid);
}
/**
* Get a {@link Player} based on vanilla selectors.
* https://minecraft.gamepedia.com/Commands#Target_selectors
*
* @param playerSelector A target selector, usually starts with an '@'.
* @param sender Target sender for selector.
* @return The player if only one found, else null.
*/
@Nullable
public static Player getBySelector(@NotNull String playerSelector,
@NotNull CommandSender sender) {
List<Player> matchedPlayers = getMultiBySelector(playerSelector, sender);
if (matchedPlayers == null || matchedPlayers.isEmpty()) {
Logging.warning("No player found with selector '%s' for %s.", playerSelector, sender.getName());
return null;
}
if (matchedPlayers.size() > 1) {
Logging.warning("Ambiguous selector result '%s' for %s (more than one player matched) - %s",
playerSelector, sender.getName(), matchedPlayers.toString());
return null;
}
return matchedPlayers.get(0);
}
/**
* Get multiple {@link Player} based on selector.
* https://minecraft.gamepedia.com/Commands#Target_selectors
*
* @param playerSelector A target selector, usually starts with an '@'.
* @param sender Target sender for selector.
* @return A list of all the {@link Player} found.
*/
@Nullable
public static List<Player> getMultiBySelector(@NotNull String playerSelector,
@NotNull CommandSender sender) {
if (playerSelector.charAt(0) != '@') {
return null;
}
try {
return Bukkit.selectEntities(sender, playerSelector).stream()
.filter(e -> e instanceof Player)
.map(e -> ((Player) e))
.collect(Collectors.toList());
} catch (IllegalArgumentException e) {
Logging.warning("An error occurred while parsing selector '%s' for %s. Is it is the correct format?",
playerSelector, sender.getName());
e.printStackTrace();
return null;
}
}
}
@@ -0,0 +1,140 @@
package com.onarandombox.MultiverseCore.utils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Utility class used to help in doing various reflection actions.
*/
public class ReflectHelper {
/**
* Try to get the {@link Class} based on its classpath.
*
* @param classPath The target classpath.
* @return A {@link Class} if found, else null.
*/
@Nullable
public static Class<?> getClass(String classPath) {
try {
return Class.forName(classPath);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Check if the {@link Class} for a give classpath is present/valid.
*
* @param classPath Target classpath.
* @return True if class path is a valid class, else false.
*/
public static boolean hasClass(String classPath) {
return getClass(classPath) != null;
}
/**
* Try to get a {@link Method} from a given class.
*
* @param clazz The class to search the method on.
* @param methodName Name of the method to get.
* @param parameterTypes Parameters present for that method.
* @param <C> The class type.
* @return A {@link Method} if found, else null.
*/
@Nullable
public static <C> Method getMethod(Class<C> clazz, String methodName, Class<?>... parameterTypes) {
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Try to get a {@link Method} from a given class.
*
* @param classInstance Instance of the class to search the method on.
* @param methodName Name of the method to get.
* @param parameterTypes Parameters present for that method.
* @param <C> The class type.
* @return A {@link Method} if found, else null.
*/
@Nullable
public static <C> Method getMethod(C classInstance, String methodName, Class<?>... parameterTypes) {
return getMethod(classInstance.getClass(), methodName, parameterTypes);
}
/**
* Calls a {@link Method}.
*
* @param classInstance Instance of the class responsible for the method.
* @param method The method to call.
* @param parameters Parameters needed when calling the method.
* @param <C> The class type.
* @param <R> The return type.
* @return Return value of the method call if any, else null.
*/
@Nullable
public static <C, R> R invokeMethod(C classInstance, Method method, Object...parameters) {
try {
return (R) method.invoke(classInstance, parameters);
} catch (Exception e) {
return null;
}
}
/**
* Try to get a {@link Field} from a given class.
*
* @param clazz The class to search the field on.
* @param fieldName Name of the field to get.
* @param <C> The class type.
* @return A {@link Field} if found, else null.
*/
@Nullable
public static <C> Field getField(Class<C> clazz, String fieldName) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
return null;
}
}
/**
* Try to get a {@link Field} from a given class.
*
* @param classInstance Instance of the class to search the field on.
* @param fieldName Name of the field to get.
* @param <C> The class type.
* @return A {@link Field} if found, else null.
*/
@Nullable
public static <C> Field getField(C classInstance, String fieldName) {
return getField(classInstance.getClass(), fieldName);
}
/**
* Gets the value of an {@link Field} from an instance of the class responsible.
*
* @param classInstance Instance of the class to get the field value from.
* @param field The field to get value from.
* @param <C> The class type.
* @param <V> The field value type.
* @return The field value if any, else null.
*/
@Nullable
public static <C, V> V getFieldValue(C classInstance, Field field) {
try {
return (V) field.get(classInstance);
} catch (IllegalAccessException e) {
return null;
}
}
}
@@ -40,6 +40,15 @@ public class SimpleLocationManipulation implements LocationManipulation {
orientationInts.put("w", 90);
orientationInts.put("nw", 135);
orientationInts.put("north", 180);
orientationInts.put("northeast", 225);
orientationInts.put("east", 270);
orientationInts.put("southeast", 315);
orientationInts.put("south", 0);
orientationInts.put("southwest", 45);
orientationInts.put("west", 90);
orientationInts.put("northwest", 135);
// "freeze" the map:
ORIENTATION_INTS = Collections.unmodifiableMap(orientationInts);
// END CHECKSTYLE-SUPPRESSION: MagicNumberCheck
@@ -36,19 +36,20 @@ import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Stack;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Public facing API to add/remove Multiverse worlds.
@@ -59,6 +60,7 @@ public class WorldManager implements MVWorldManager {
private final WorldPurger worldPurger;
private final Map<String, MultiverseWorld> worlds;
private Map<String, WorldProperties> worldsFromTheConfig;
private Set<String> defaultWorldNames;
private FileConfiguration configWorlds = null;
private Map<String, String> defaultGens;
private String firstSpawn;
@@ -130,7 +132,7 @@ public class WorldManager implements MVWorldManager {
}
// Check for valid world name
if (!(isValidWorldName(oldName) && isValidWorldName(newName))) {
if (!(WorldNameChecker.isValidWorldName(oldName) && WorldNameChecker.isValidWorldName(oldName))) {
return false;
}
@@ -238,7 +240,9 @@ public class WorldManager implements MVWorldManager {
return false;
}
if (!isValidWorldName(name)) {
if (!WorldNameChecker.isValidWorldName(name)) {
Logging.warning("Invalid world name '" + name + "'");
Logging.warning("World name should not contain spaces or special characters!");
return false;
}
@@ -381,6 +385,11 @@ public class WorldManager implements MVWorldManager {
*/
@Override
public boolean unloadWorld(String name, boolean unloadBukkit) {
if (this.isDefaultWorld(name)) {
Logging.warning("You cannot unload the default worlds.");
Logging.warning("Default worlds on your server are: %s", this.defaultWorldNames);
return false;
}
if (this.worlds.containsKey(name)) {
this.worldsFromTheConfig.get(name).cacheVirtualProperties();
if (unloadBukkit && this.unloadWorldFromBukkit(name, true)) {
@@ -424,21 +433,6 @@ public class WorldManager implements MVWorldManager {
}
}
/**
* Check if the world name is allowed
*
* @param name Name of the world
* @return True if the world world name is valid based on regex
*/
private boolean isValidWorldName(String name) {
if (!worldNamePattern.matcher(name).matches()) {
Logging.warning("Invalid world name '" + name + "'");
Logging.warning("World name should not contain spaces or special characters!");
return false;
}
return true;
}
private void brokenWorld(String name) {
Logging.severe("The world '" + name + "' could NOT be loaded because it contains errors and is probably corrupt!");
Logging.severe("Try using Minecraft Region Fixer to repair your world! '" + name + "'");
@@ -630,6 +624,14 @@ public class WorldManager implements MVWorldManager {
*/
@Override
public MultiverseWorld getMVWorld(String name) {
return this.getMVWorld(name, true);
}
/**
* {@inheritDoc}
*/
@Override
public MultiverseWorld getMVWorld(String name, boolean checkAliases) {
if (name == null) {
return null;
}
@@ -637,7 +639,7 @@ public class WorldManager implements MVWorldManager {
if (world != null) {
return world;
}
return this.getMVWorldByAlias(name);
return (checkAliases) ? this.getMVWorldByAlias(name) : null;
}
/**
@@ -646,7 +648,7 @@ public class WorldManager implements MVWorldManager {
@Override
public MultiverseWorld getMVWorld(World world) {
if (world != null) {
return this.getMVWorld(world.getName());
return this.getMVWorld(world.getName(), false);
}
return null;
}
@@ -671,7 +673,15 @@ public class WorldManager implements MVWorldManager {
*/
@Override
public boolean isMVWorld(final String name) {
return (this.worlds.containsKey(name) || isMVWorldAlias(name));
return this.isMVWorld(name, true);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isMVWorld(final String name, boolean checkAliases) {
return this.worlds.containsKey(name) || (checkAliases && this.isMVWorldAlias(name));
}
/**
@@ -704,6 +714,7 @@ public class WorldManager implements MVWorldManager {
public void loadDefaultWorlds() {
this.ensureConfigIsPrepared();
List<World> myWorlds = this.plugin.getServer().getWorlds();
this.defaultWorldNames = new HashSet<>(myWorlds.size());
for (World w : myWorlds) {
String name = w.getName();
if (!worldsFromTheConfig.containsKey(name)) {
@@ -711,9 +722,15 @@ public class WorldManager implements MVWorldManager {
if (this.defaultGens.containsKey(name)) {
generator = this.defaultGens.get(name);
}
this.addWorld(name, w.getEnvironment(), String.valueOf(w.getSeed()), w.getWorldType(), w.canGenerateStructures(), generator);
if (!this.addWorld(name, w.getEnvironment(), String.valueOf(w.getSeed()), w.getWorldType(), w.canGenerateStructures(), generator)) {
Logging.warning("Error loading default world '%s'!", w.getName());
continue;
}
}
this.defaultWorldNames.add(name);
}
this.defaultWorldNames = Collections.unmodifiableSet(this.defaultWorldNames);
Logging.fine("Loaded default worlds: %s", this.defaultWorldNames);
}
private void ensureConfigIsPrepared() {
@@ -723,6 +740,21 @@ public class WorldManager implements MVWorldManager {
}
}
@Override
public boolean isDefaultWorld(MultiverseWorld world) {
return world != null && this.isDefaultWorld(world.getName());
}
@Override
public boolean isDefaultWorld(String worldName) {
return this.defaultWorldNames != null && this.defaultWorldNames.contains(worldName);
}
@Override
public Collection<String> getDefaultWorldNames() {
return this.defaultWorldNames;
}
/**
* {@inheritDoc}
*/
@@ -953,4 +985,21 @@ public class WorldManager implements MVWorldManager {
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getPotentialWorlds() {
File worldContainer = this.plugin.getServer().getWorldContainer();
if (worldContainer == null) {
return Collections.emptyList();
}
return Arrays.stream(worldContainer.listFiles())
.filter(File::isDirectory)
.filter(folder -> !this.isMVWorld(folder.getName(), false))
.filter(WorldNameChecker::isValidWorldFolder)
.map(File::getName)
.collect(Collectors.toList());
}
}
@@ -0,0 +1,159 @@
package com.onarandombox.MultiverseCore.utils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
/**
* <p>Utility class in helping to check the status of a world name and it's associated world folder.</p>
*
* <p>Note this is for preliminary checks and better command output. A valid result will suggest but not
* 100% determine that a world name can be created, loaded or imported.</p>
*/
public class WorldNameChecker {
private static final Pattern WORLD_NAME_PATTERN = Pattern.compile("[a-zA-Z0-9/._-]+");
private static final Set<String> BLACKLIST_NAMES = Collections.unmodifiableSet(new HashSet<String>() {{
add("plugins");
add("logs");
add("cache");
add("crash-reports");
}});
/**
* Checks if a world name is valid.
*
* @param worldName The world name to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldName(@Nullable String worldName) {
return checkName(worldName) == NameStatus.VALID;
}
/**
* Checks the current validity status of a world name.
*
* @param worldName The world name to check on.
* @return The resulting name status.
*/
@NotNull
public static NameStatus checkName(@Nullable String worldName) {
if (BLACKLIST_NAMES.contains(worldName)) {
return NameStatus.BLACKLISTED;
}
if (worldName == null || !WORLD_NAME_PATTERN.matcher(worldName).matches()) {
return NameStatus.INVALID_CHARS;
}
return NameStatus.VALID;
}
/**
* Checks if a world name has a valid world folder.
*
* @param worldName The world name to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldFolder(@Nullable String worldName) {
return checkFolder(worldName) == FolderStatus.VALID;
}
/**
* Checks if a world folder is valid.
*
* @param worldFolder The world folder to check on.
* @return True if check result is valid, else false.
*/
public static boolean isValidWorldFolder(@Nullable File worldFolder) {
return checkFolder(worldFolder) == FolderStatus.VALID;
}
/**
* Checks the current folder status for a world name.
*
* @param worldName The world name to check on.
* @return The resulting folder status.
*/
@NotNull
public static FolderStatus checkFolder(@Nullable String worldName) {
if (worldName == null) {
return FolderStatus.DOES_NOT_EXIST;
}
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
return checkFolder(worldFolder);
}
/**
* Checks the current folder status.
*
* @param worldFolder The world folder to check on.
* @return The resulting folder status.
*/
@NotNull
public static FolderStatus checkFolder(@Nullable File worldFolder) {
if (worldFolder == null || !worldFolder.exists() || !worldFolder.isDirectory()) {
return FolderStatus.DOES_NOT_EXIST;
}
if (!folderHasDat(worldFolder)) {
return FolderStatus.NOT_A_WORLD;
}
return FolderStatus.VALID;
}
/**
* A very basic check to see if a folder has a level.dat file. If it does, we can safely assume
* it's a world folder.
*
* @param worldFolder The File that may be a world.
* @return True if it looks like a world, else false.
*/
private static boolean folderHasDat(@NotNull File worldFolder) {
File[] files = worldFolder.listFiles((file, name) -> name.toLowerCase().endsWith(".dat"));
return files != null && files.length > 0;
}
/**
* Result after checking validity of world name.
*/
public enum NameStatus {
/**
* Name is valid.
*/
VALID,
/**
* Name not valid as it contains invalid characters.
*/
INVALID_CHARS,
/**
* Name not valid as it is deemed blacklisted.
*/
BLACKLISTED
}
/**
* Result after checking validity of world folder.
*/
public enum FolderStatus {
/**
* Folder is valid.
*/
VALID,
/**
* Folder exist, but contents in it doesnt look like a world.
*/
NOT_A_WORLD,
/**
* Folder does not exist.
*/
DOES_NOT_EXIST
}
}
@@ -50,6 +50,10 @@ public class TestEntryFeeConversion {
WorldProperties props = new WorldProperties(config);
assertNull(props.entryfee.getCurrency());
entryFee.put("currency", 0);
props = new WorldProperties(config);
assertNull(props.entryfee.getCurrency());
entryFee.put("currency", 1);
props = new WorldProperties(config);
assertEquals(Material.STONE, props.entryfee.getCurrency());
@@ -214,11 +214,18 @@ public class TestInstanceCreator {
serverfield.set(core, mockServer);
// Set buscript
Buscript buscript = PowerMockito.spy(new Buscript(core));
Buscript buscript;
Field buscriptfield = MultiverseCore.class.getDeclaredField("buscript");
buscriptfield.setAccessible(true);
try {
buscript = PowerMockito.spy(new Buscript(core));
when(buscript.getPlugin()).thenReturn(core);
} catch (NullPointerException e) {
buscript = null;
}
buscriptfield.set(core, buscript);
when(buscript.getPlugin()).thenReturn(core);
// Set worldManager
WorldManager wm = PowerMockito.spy(new WorldManager(core));