More complete Javadoc
This commit is contained in:
parent
3d92c3afb6
commit
8f5f880754
@ -7,6 +7,9 @@ import net.kyori.adventure.text.ComponentLike;
|
||||
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
|
||||
/**
|
||||
* Utility class to ease conversion between our Adventure backed Chat API and BungeeCord chat API.
|
||||
*/
|
||||
public class ChatBungee {
|
||||
|
||||
|
||||
|
@ -11,36 +11,107 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Convenient enum to uses legacy format while keeping compatibility with modern chat format and API (Adventure, ...)
|
||||
*/
|
||||
public enum LegacyChatFormat {
|
||||
|
||||
/**
|
||||
* Black (0) color format code.
|
||||
*/
|
||||
BLACK('0'),
|
||||
/**
|
||||
* Dark blue (1) color format code.
|
||||
*/
|
||||
DARK_BLUE('1'),
|
||||
/**
|
||||
* Dark green (2) color format code.
|
||||
*/
|
||||
DARK_GREEN('2'),
|
||||
/**
|
||||
* Dark aqua (3) color format code.
|
||||
*/
|
||||
DARK_AQUA('3'),
|
||||
/**
|
||||
* Dark red (4) color format code.
|
||||
*/
|
||||
DARK_RED('4'),
|
||||
/**
|
||||
* Dark purple (5) color format code.
|
||||
*/
|
||||
DARK_PURPLE('5'),
|
||||
/**
|
||||
* Gold (6) color format code.
|
||||
*/
|
||||
GOLD('6'),
|
||||
/**
|
||||
* Gray (7) color format code.
|
||||
*/
|
||||
GRAY('7'),
|
||||
/**
|
||||
* Dark gray (8) color format code.
|
||||
*/
|
||||
DARK_GRAY('8'),
|
||||
/**
|
||||
* Blue (9) color format code.
|
||||
*/
|
||||
BLUE('9'),
|
||||
/**
|
||||
* Green (A) color format code.
|
||||
*/
|
||||
GREEN('a'),
|
||||
/**
|
||||
* Aqua (B) color format code.
|
||||
*/
|
||||
AQUA('b'),
|
||||
/**
|
||||
* Red (C) color format code.
|
||||
*/
|
||||
RED('c'),
|
||||
/**
|
||||
* Light purple (D) color format code.
|
||||
*/
|
||||
LIGHT_PURPLE('d'),
|
||||
/**
|
||||
* Yellow (E) color format code.
|
||||
*/
|
||||
YELLOW('e'),
|
||||
/**
|
||||
* White (F) color format code.
|
||||
*/
|
||||
WHITE('f'),
|
||||
MAGIC('k'),
|
||||
/**
|
||||
* Obfuscated (K) decoration format code.
|
||||
*/
|
||||
OBFUSCATED('k'),
|
||||
/**
|
||||
* Bold (L) decoration format code.
|
||||
*/
|
||||
BOLD('l'),
|
||||
/**
|
||||
* Strikethrough (M) decoration format code.
|
||||
*/
|
||||
STRIKETHROUGH('m'),
|
||||
/**
|
||||
* Underlined (N) decoration format code.
|
||||
*/
|
||||
UNDERLINED('n'),
|
||||
/**
|
||||
* Italic (O) decoration format code.
|
||||
*/
|
||||
ITALIC('o'),
|
||||
/**
|
||||
* Reset (R) format code.
|
||||
*/
|
||||
RESET('r');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The character used by Minecraft for legacy chat format.
|
||||
*/
|
||||
public static final char COLOR_CHAR = LegacyComponentSerializer.SECTION_CHAR;
|
||||
|
||||
/** {@link #COLOR_CHAR} but as a String! */
|
||||
public static final String COLOR_STR_PREFIX = Character.toString(COLOR_CHAR);
|
||||
|
||||
private static final Map<Character, LegacyChatFormat> BY_CHAR;
|
||||
@ -48,10 +119,20 @@ public enum LegacyChatFormat {
|
||||
private static final Map<LegacyFormat, LegacyChatFormat> BY_LEGACY;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the {@link LegacyChatFormat} from the provided chat color code.
|
||||
* @param code the character code from [0-9A-Fa-fK-Ok-oRr].
|
||||
* @return the {@link LegacyChatFormat} related to the provided code.
|
||||
*/
|
||||
public static LegacyChatFormat of(char code) {
|
||||
return BY_CHAR.get(Character.toLowerCase(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link LegacyChatFormat} from the provided {@link TextFormat} instance.
|
||||
* @param format the {@link TextFormat} instance.
|
||||
* @return the {@link LegacyChatFormat} related to the provided format.
|
||||
*/
|
||||
public static LegacyChatFormat of(TextFormat format) {
|
||||
LegacyChatFormat colorOrDecoration = BY_FORMAT.get(format);
|
||||
if (colorOrDecoration != null)
|
||||
@ -61,19 +142,24 @@ public enum LegacyChatFormat {
|
||||
throw new IllegalArgumentException("Unsupported format of type " + format.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link LegacyChatFormat} from the provided {@link LegacyFormat} instance.
|
||||
* @param advLegacy the {@link LegacyFormat} instance.
|
||||
* @return the {@link LegacyChatFormat} related to the provided format.
|
||||
*/
|
||||
public static LegacyChatFormat of(LegacyFormat advLegacy) {
|
||||
return BY_LEGACY.get(advLegacy);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The format code of this chat format.
|
||||
*/
|
||||
public final char code;
|
||||
|
||||
/**
|
||||
* The Adventure legacy format instance related to this chat format.
|
||||
*/
|
||||
public final LegacyFormat advLegacyFormat;
|
||||
|
||||
LegacyChatFormat(char code) {
|
||||
@ -81,22 +167,42 @@ public enum LegacyChatFormat {
|
||||
advLegacyFormat = LegacyComponentSerializer.parseChar(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the related {@link TextColor}, or null if it's not a color.
|
||||
* @return the related {@link TextColor}, or null if it's not a color.
|
||||
*/
|
||||
public TextColor getTextColor() {
|
||||
return advLegacyFormat.color();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this format is a color.
|
||||
* @return true if this format is a color, false otherwise.
|
||||
*/
|
||||
public boolean isColor() {
|
||||
return getTextColor() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the related {@link TextDecoration}, or null if it's not a decoration.
|
||||
* @return the related {@link TextDecoration}, or null if it's not a decoration.
|
||||
*/
|
||||
public TextDecoration getTextDecoration() {
|
||||
return advLegacyFormat.decoration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this format is a decoration (bold, italic, ...).
|
||||
* @return true if this format is a decoration, false otherwise.
|
||||
*/
|
||||
public boolean isDecoration() {
|
||||
return getTextDecoration() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if this format is the reset.
|
||||
* @return true if this format is the reset, false otherwise.
|
||||
*/
|
||||
public boolean isReset() {
|
||||
return this == RESET;
|
||||
}
|
||||
@ -109,14 +215,16 @@ public enum LegacyChatFormat {
|
||||
|
||||
|
||||
static {
|
||||
BY_CHAR = Arrays.stream(values()).sequential().collect(Collectors.toMap(e -> e.code, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
BY_CHAR = Arrays.stream(values()).sequential()
|
||||
.collect(Collectors.toMap(e -> e.code, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
BY_FORMAT = Arrays.stream(values()).sequential()
|
||||
.filter(e -> e.isColor() || e.isDecoration())
|
||||
.collect(Collectors.toMap(e -> {
|
||||
if (e.isColor())
|
||||
return e.getTextColor();
|
||||
return e.getTextDecoration();
|
||||
}, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
BY_LEGACY = Arrays.stream(values()).sequential().collect(Collectors.toMap(e -> e.advLegacyFormat, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
if (e.isColor())
|
||||
return e.getTextColor();
|
||||
return e.getTextDecoration();
|
||||
}, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
BY_LEGACY = Arrays.stream(values()).sequential()
|
||||
.collect(Collectors.toMap(e -> e.advLegacyFormat, e -> e, (e1, e2) -> e1, LinkedHashMap::new));
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,60 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A basic class holding configuration for {@link PaperBackupManager}.
|
||||
*/
|
||||
@SuppressWarnings("CanBeFinal")
|
||||
public class PaperBackupConfig {
|
||||
|
||||
/**
|
||||
* Set to true to enable worlds backup.
|
||||
* Defaults to true.
|
||||
*/
|
||||
public boolean worldBackupEnabled = true;
|
||||
|
||||
/**
|
||||
* Set to true to enable the backup of the working directory.
|
||||
* The workdir backup will already ignore the logs directory and any world folder (folder with a level.dat file in it).
|
||||
* Defaults to true.
|
||||
*/
|
||||
public boolean workdirBackupEnabled = true;
|
||||
|
||||
/**
|
||||
* Set to true to enable the backup of logs.
|
||||
* Defaults to true.
|
||||
*/
|
||||
public boolean logsBackupEnabled = true;
|
||||
|
||||
/**
|
||||
* The cron-formatted scheduling of the worlds and workdir backups.
|
||||
* The default value is {@code "0 2 * * *"}, that is every day at 2am.
|
||||
*/
|
||||
public String scheduling = "0 2 * * *"; // cron format, here is every day at 2am
|
||||
|
||||
/**
|
||||
* The backup target directory.
|
||||
* Must be set (defaults to null).
|
||||
*/
|
||||
public File backupDirectory = null;
|
||||
|
||||
/**
|
||||
* The backup cleaner for the worlds backup.
|
||||
* Defaults to keep 1 backup every 3 month + the last 5 backups.
|
||||
*/
|
||||
public BackupCleaner worldBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||
|
||||
/**
|
||||
* The backup cleaner for the workdir backup.
|
||||
* Defaults to keep 1 backup every 3 month + the last 5 backups.
|
||||
*/
|
||||
public BackupCleaner workdirBackupCleaner = BackupCleaner.KEEPING_1_EVERY_N_MONTH(3).merge(BackupCleaner.KEEPING_N_LAST(5));
|
||||
|
||||
/**
|
||||
* The list of files or directory to ignore.
|
||||
* Defaults to none.
|
||||
* The workdir backup will already ignore the logs directory and any world folder (folder with a level.dat file in it).
|
||||
*/
|
||||
public List<String> workdirIgnoreList = new ArrayList<>();
|
||||
|
||||
}
|
||||
|
@ -23,12 +23,19 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
/**
|
||||
* The backup manager for Paper servers.
|
||||
*/
|
||||
public class PaperBackupManager extends BackupManager implements Listener {
|
||||
|
||||
private final Map<String, PaperWorldProcess> compressWorlds = new HashMap<>();
|
||||
|
||||
PaperBackupConfig config;
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate a new backup manager.
|
||||
* @param config the configuration of the backups.
|
||||
*/
|
||||
public PaperBackupManager(PaperBackupConfig config) {
|
||||
super(config.backupDirectory);
|
||||
setConfig(config);
|
||||
@ -49,13 +56,17 @@ public class PaperBackupManager extends BackupManager implements Listener {
|
||||
super.addProcess(process);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the backups config
|
||||
* @param config the new config.
|
||||
*/
|
||||
public void setConfig(PaperBackupConfig config) {
|
||||
this.config = config;
|
||||
backupQueue.forEach(this::updateProcessConfig);
|
||||
}
|
||||
|
||||
|
||||
public void updateProcessConfig(BackupProcess process) {
|
||||
private void updateProcessConfig(BackupProcess process) {
|
||||
if (process instanceof PaperWorkdirProcess) {
|
||||
process.setEnabled(config.workdirBackupEnabled);
|
||||
process.setBackupCleaner(config.workdirBackupCleaner);
|
||||
|
@ -63,6 +63,7 @@ public class DirectionalVector {
|
||||
* contained in the provided {@link Location}.
|
||||
* {@link Location#getYaw()} and {@link Location#getPitch()} values are automatically
|
||||
* converted to conform {@link #yaw} and {@link #pitch} specification.
|
||||
* @param l the location.
|
||||
*/
|
||||
public DirectionalVector(Location l) {
|
||||
this(
|
||||
@ -79,6 +80,7 @@ public class DirectionalVector {
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link DirectionalVector} from a simple {@link Vector}.
|
||||
* @param v the vector representing the direction. If v.getX() and v.getZ() are 0,
|
||||
* the yaw will be 0. This may have inconsistency if the vector is calculated
|
||||
* from a {@link Location}'s yaw and pitch. In this case, prefer using
|
||||
@ -126,7 +128,10 @@ public class DirectionalVector {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets a Vector using the internal X, Y and Z values, that is a simple directional 3D vector.
|
||||
* @return this vector as a simple 3D {@link Vector}.
|
||||
*/
|
||||
public Vector toVector() {
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
@ -135,7 +140,8 @@ public class DirectionalVector {
|
||||
/**
|
||||
* Set the yaw and the pitch of the provided {@link Location}
|
||||
* with the values inside the current {@link DirectionalVector}
|
||||
* after conversion of these values
|
||||
* after conversion of these values.
|
||||
* @param l the location.
|
||||
*/
|
||||
public void putIntoLocation(Location l) {
|
||||
/* std : -PI/2 : <0 ? +2PI : MC
|
||||
@ -148,7 +154,10 @@ public class DirectionalVector {
|
||||
l.setPitch((float) Math.toDegrees(-pitch));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the vector pointing to the opposite direction.
|
||||
* @return the opposite vector.
|
||||
*/
|
||||
public DirectionalVector getOpposite() {
|
||||
return new DirectionalVector(
|
||||
-x,
|
||||
@ -163,6 +172,7 @@ public class DirectionalVector {
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the back of the head.
|
||||
* This is an alias of {@link #getOpposite()}
|
||||
* @return the opposite of this vector.
|
||||
*/
|
||||
public DirectionalVector getBackDirection() {
|
||||
return getOpposite();
|
||||
@ -171,6 +181,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the bottom of the head.
|
||||
* @return the vector pointing on the bottom, as if this vector was the front orientation of a player head.
|
||||
*/
|
||||
public DirectionalVector getBottomDirection() {
|
||||
return new DirectionalVector(
|
||||
@ -182,6 +193,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the top of the head.
|
||||
* @return the vector pointing on the top, as if this vector was the front orientation of a player head.
|
||||
*/
|
||||
public DirectionalVector getTopDirection() {
|
||||
return new DirectionalVector(
|
||||
@ -194,6 +206,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the left of the head.
|
||||
* @return the vector pointing on the left, as if this vector was the front orientation of a player head.
|
||||
*/
|
||||
public DirectionalVector getLeftDirection() {
|
||||
return new DirectionalVector(
|
||||
@ -206,6 +219,7 @@ public class DirectionalVector {
|
||||
/**
|
||||
* If the current direction is the player face direction,
|
||||
* this method return the direction of the right of the head.
|
||||
* @return the vector pointing on the right, as if this vector was the front orientation of a player head.
|
||||
*/
|
||||
public DirectionalVector getRightDirection() {
|
||||
return new DirectionalVector(
|
||||
|
@ -2,24 +2,29 @@ package fr.pandacube.lib.paper.geometry;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.RayTraceResult;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
/**
|
||||
* Utility class related to geometry and Minecraft.
|
||||
*/
|
||||
public class GeometryUtil {
|
||||
|
||||
|
||||
/**
|
||||
* Value equal to <code>{@link Math#PI}</code>.
|
||||
*/
|
||||
public static final double PI = Math.PI;
|
||||
|
||||
static final double PI = Math.PI;
|
||||
|
||||
/**
|
||||
* Value equal to <code>{@link Math#PI} / 2</code>.
|
||||
*/
|
||||
public static final double PId2 = PI/2;
|
||||
|
||||
static final double PId2 = PI/2;
|
||||
|
||||
/**
|
||||
* Value equal to <code>{@link Math#PI} * 2</code>.
|
||||
*/
|
||||
public static final double PIx2 = PI*2;
|
||||
static final double PIx2 = PI*2;
|
||||
|
||||
|
||||
|
||||
@ -55,9 +60,21 @@ public class GeometryUtil {
|
||||
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
|
||||
*/
|
||||
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27;
|
||||
/**
|
||||
* The size of a skin pixel.
|
||||
*/
|
||||
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
|
||||
/**
|
||||
* The height of the center of rotation of the head, that is the distance between neck and the player's foot.
|
||||
*/
|
||||
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
|
||||
/**
|
||||
* The height of the center of rotation of the head, that is the distance between neck and the player's foot, but when the player is sneaking.
|
||||
*/
|
||||
public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK);
|
||||
/**
|
||||
* The size of the first layer of the players head.
|
||||
*/
|
||||
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
|
||||
|
||||
|
||||
@ -67,7 +84,6 @@ public class GeometryUtil {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -76,7 +92,7 @@ public class GeometryUtil {
|
||||
/**
|
||||
* Get the {@link Location}s of the 8 vertex of the player head<br/>
|
||||
* This method only work if the player is standing up
|
||||
* (not dead, not gliding, not sleeping).
|
||||
* (not dead, not gliding, not sleeping, not swimming).
|
||||
* @param playerLocation the location of the player, generally provided by {@link Player#getLocation()}
|
||||
* @param isSneaking if the player is sneaking. Generally {@link Player#isSneaking()}
|
||||
* @return an array of 8 {@link Location}s with x, y, and z values filled (yaw and pitch are ignored).
|
||||
@ -129,27 +145,22 @@ public class GeometryUtil {
|
||||
/**
|
||||
* Check if the path from <i>start</i> location to <i>end</i> pass through
|
||||
* the axis aligned bounding box defined by <i>min</i> and <i>max</i>.
|
||||
* @param start the start of the path.
|
||||
* @param end the end of the path.
|
||||
* @param min the min of the bounding box.
|
||||
* @param max the max of the bounding box.
|
||||
* @return true if the path intersects the bounding box.
|
||||
* @deprecated use {@link BoundingBox#rayTrace(Vector, Vector, double)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean hasIntersection(Vector start, Vector end, Vector min, Vector max) {
|
||||
final double epsilon = 0.0001f;
|
||||
|
||||
Vector d = end.clone().subtract(start).multiply(0.5);
|
||||
Vector e = max.clone().subtract(min).multiply(0.5);
|
||||
Vector c = start.clone().add(d).subtract(min.clone().add(max).multiply(0.5));
|
||||
Vector ad = d.clone();
|
||||
ad.setX(Math.abs(ad.getX()));
|
||||
ad.setY(Math.abs(ad.getY()));
|
||||
ad.setZ(Math.abs(ad.getZ()));
|
||||
|
||||
return !(
|
||||
Math.abs(c.getX()) > e.getX() + ad.getX()
|
||||
|| Math.abs(c.getY()) > e.getY() + ad.getY()
|
||||
|| Math.abs(c.getZ()) > e.getX() + ad.getZ()
|
||||
|| Math.abs(d.getY() * c.getZ() - d.getZ() * c.getY()) > e.getY() * ad.getZ() + e.getZ() * ad.getY() + epsilon
|
||||
|| Math.abs(d.getZ() * c.getX() - d.getX() * c.getZ()) > e.getZ() * ad.getX() + e.getX() * ad.getZ() + epsilon
|
||||
|| Math.abs(d.getX() * c.getY() - d.getY() * c.getX()) > e.getX() * ad.getY() + e.getY() * ad.getX() + epsilon
|
||||
);
|
||||
RayTraceResult res = BoundingBox.of(min, max).rayTrace(start, end.clone().subtract(start), end.distance(start));
|
||||
return res != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private GeometryUtil() {}
|
||||
|
||||
|
||||
}
|
||||
|
@ -2,11 +2,10 @@ package fr.pandacube.lib.paper.geometry.blocks;
|
||||
|
||||
import fr.pandacube.lib.util.RandomUtil;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
@ -30,10 +29,19 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
volume = original.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link Vector}.
|
||||
* @param p1 the first vector.
|
||||
* @param p2 the second vector.
|
||||
*/
|
||||
public AABBBlock(Vector p1, Vector p2) {
|
||||
this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link AABBBlock} based on the provided Bukkit's {@link BoundingBox}.
|
||||
* @param bb the bounding box.
|
||||
*/
|
||||
public AABBBlock(BoundingBox bb) {
|
||||
pos1 = bb.getMin();
|
||||
pos2 = bb.getMax();
|
||||
@ -41,15 +49,35 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
volume = (int) bb.getVolume();
|
||||
bukkitBoundingBox = bb;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link Location}.
|
||||
* The worlds defined in the provided locations are ignored.
|
||||
* @param l1 the first location.
|
||||
* @param l2 the second location.
|
||||
*/
|
||||
public AABBBlock(Location l1, Location l2) {
|
||||
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link AABBBlock} based on the two provided Bukkit's {@link BlockVector}.
|
||||
* @param l1 the first block vector.
|
||||
* @param l2 the second block vector.
|
||||
*/
|
||||
public AABBBlock(BlockVector l1, BlockVector l2) {
|
||||
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@link AABBBlock} based on the individual coordinates of the 2 vectors.
|
||||
* @param p1x the x value of the first vector.
|
||||
* @param p1y the y value of the first vector.
|
||||
* @param p1z the z value of the first vector.
|
||||
* @param p2x the x value of the second vector.
|
||||
* @param p2y the y value of the second vector.
|
||||
* @param p2z the z value of the second vector.
|
||||
*/
|
||||
public AABBBlock(int p1x, int p1y, int p1z, int p2x, int p2y, int p2z) {
|
||||
/*
|
||||
* Prends les points extérieurs permettant de former un bounding box englobant
|
||||
@ -74,22 +102,45 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the "minimum" {@link Vector}, that is the vector with the lowest coordinates that is inside this bounding box.
|
||||
* @return the minimum vector.
|
||||
*/
|
||||
public Vector getMin() {
|
||||
return pos1.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the "maximum" {@link Vector}, that is the vector with the highest coordinates that is inside this bounding box.
|
||||
* @return the maximum vector.
|
||||
*/
|
||||
public Vector getMax() {
|
||||
return pos2.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link BlockVector} with the lowest coordinates in this bounding box.
|
||||
* @return the minimum block vector.
|
||||
*/
|
||||
public BlockVector getMinBlock() {
|
||||
return pos1.toBlockVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link BlockVector} with the highest coordinates in this bounding box.
|
||||
* @return the maximum block vector.
|
||||
*/
|
||||
public BlockVector getMaxBlock() {
|
||||
return pos2.clone().add(new Vector(-1, -1, -1)).toBlockVector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new {@link AABBBlock} with its coordinates shifted by the provided amount.
|
||||
* @param x the x shift.
|
||||
* @param y the y shift.
|
||||
* @param z the z shift.
|
||||
* @return a shifted bounding box.
|
||||
*/
|
||||
public AABBBlock shift(int x, int y, int z) {
|
||||
return new AABBBlock(this, x, y, z);
|
||||
}
|
||||
@ -101,7 +152,6 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean overlaps(BoundingBox bb) {
|
||||
return asBukkitBoundingBox().overlaps(bb);
|
||||
}
|
||||
@ -109,15 +159,23 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
public boolean isInside(Vector v) {
|
||||
return asBukkitBoundingBox().contains(v);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the coordinate of the center of this bounding box.
|
||||
* @return the center of this bounding box.
|
||||
*/
|
||||
public Vector getCenter() {
|
||||
return center.clone();
|
||||
}
|
||||
|
||||
|
||||
public long getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the Bukkit equivalent of this bounding box.
|
||||
* @return a {@link BoundingBox} corresponding to this {@link AABBBlock}.
|
||||
*/
|
||||
public BoundingBox asBukkitBoundingBox() {
|
||||
if (bukkitBoundingBox == null) {
|
||||
bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
|
||||
@ -134,7 +192,7 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<BlockVector> iterator() {
|
||||
public @NotNull Iterator<BlockVector> iterator() {
|
||||
return new Iterator<>() {
|
||||
private int x = pos1.getBlockX(),
|
||||
y = pos1.getBlockY(),
|
||||
@ -162,22 +220,6 @@ public class AABBBlock implements BlockSet, Cloneable {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public Iterable<Block> asBlockIterable(World w) {
|
||||
return () -> new Iterator<>() {
|
||||
final Iterator<BlockVector> nested = AABBBlock.this.iterator();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nested.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Block next() {
|
||||
BlockVector bv = nested.next();
|
||||
return w.getBlockAt(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -12,19 +12,33 @@ import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A group of {@link AABBBlock}.
|
||||
*/
|
||||
public class AABBBlockGroup implements BlockSet {
|
||||
|
||||
|
||||
/**
|
||||
* The list of {@link AABBBlock} contained in this group. This list is unmodifiable.
|
||||
*/
|
||||
public final List<AABBBlock> subAABB;
|
||||
|
||||
private final AABBBlock englobingAABB;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AABBBlockGroup}.
|
||||
* @param in the list of {@link AABBBlock} that will be contained in this group.
|
||||
*/
|
||||
public AABBBlockGroup(Collection<AABBBlock> in) {
|
||||
if (in.isEmpty())
|
||||
throw new IllegalArgumentException("Provided collection must not be empty.");
|
||||
subAABB = List.copyOf(in);
|
||||
englobingAABB = initEnglobingAABB();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link AABBBlockGroup}.
|
||||
* @param in an array of {@link AABBBlock} that will be contained in this group.
|
||||
*/
|
||||
public AABBBlockGroup(AABBBlock... in) {
|
||||
this(Arrays.asList(in));
|
||||
}
|
||||
|
@ -1,23 +1,58 @@
|
||||
package fr.pandacube.lib.paper.geometry.blocks;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.util.BlockVector;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Represents a set of blocks in a world.
|
||||
*/
|
||||
public interface BlockSet extends Iterable<BlockVector> {
|
||||
|
||||
/**
|
||||
* Gets a random coordinate that is inside this block set.
|
||||
* @return a random coordinate inside this block set.
|
||||
*/
|
||||
Vector getRandomPosition();
|
||||
|
||||
/**
|
||||
* Gets the volume, in blocks (or cubic meters), of this block set.
|
||||
* @return the volume of this block set.
|
||||
*/
|
||||
long getVolume();
|
||||
|
||||
/**
|
||||
* Gets the englobing bounding box if this block set.
|
||||
* @return the englobing bounding box if this block set.
|
||||
*/
|
||||
AABBBlock getEnglobingAABB();
|
||||
|
||||
|
||||
/**
|
||||
* Tells if this block set overlaps the provided bounding box.
|
||||
* @param bb the provided bounding box
|
||||
* @return true if its overlaps, false otherwise.
|
||||
*/
|
||||
boolean overlaps(BoundingBox bb);
|
||||
/**
|
||||
* Tells if this block set overlaps the bounding box of the provided entity.
|
||||
* @param e the provided entity.
|
||||
* @return true if its overlaps, false otherwise.
|
||||
*/
|
||||
default boolean overlaps(Entity e) {
|
||||
return overlaps(e.getBoundingBox());
|
||||
}
|
||||
/**
|
||||
* Tells if this block set overlaps the provided one. that is there is at least one block in common.
|
||||
* @param bs the provided block set.
|
||||
* @return true if its overlaps, false otherwise.
|
||||
*/
|
||||
default boolean overlaps(BlockSet bs) {
|
||||
if (this instanceof AABBBlock b1) {
|
||||
if (bs instanceof AABBBlock b2)
|
||||
@ -37,20 +72,78 @@ public interface BlockSet extends Iterable<BlockVector> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tells if the provided vector is inside this bounding box.
|
||||
* @param v the vector.
|
||||
* @return true if its inside, false otherwise.
|
||||
*/
|
||||
boolean isInside(Vector v);
|
||||
|
||||
/**
|
||||
* Tells if the provided location is inside this bounding box.
|
||||
* The world of the location is ignored.
|
||||
* @param l the location.
|
||||
* @return true if its inside, false otherwise.
|
||||
*/
|
||||
default boolean isInside(Location l) {
|
||||
return isInside(l.toVector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the provided block is inside this bounding box.
|
||||
* The world of the block is ignored.
|
||||
* @param b the block.
|
||||
* @return true if its inside, false otherwise.
|
||||
*/
|
||||
default boolean isInside(Block b) {
|
||||
return isInside(b.getLocation().add(.5, .5, .5));
|
||||
}
|
||||
default boolean isInside(Entity p) {
|
||||
return isInside(p.getLocation());
|
||||
|
||||
/**
|
||||
* Tells if the provided entity is inside this bounding box.
|
||||
* It calls {@link #isInside(Location)} using the returned value of {@link Entity#getLocation()}.
|
||||
* The world of the entity is ignored.
|
||||
* @param e the entity.
|
||||
* @return true if its inside, false otherwise.
|
||||
*/
|
||||
default boolean isInside(Entity e) {
|
||||
return isInside(e.getLocation());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets an iterator iterating through all the blocks of this block set.
|
||||
* @param w the world of the blocks.
|
||||
* @return a new iterator.
|
||||
*/
|
||||
default Iterable<Block> asBlockIterable(World w) {
|
||||
return () -> new Iterator<>() {
|
||||
final Iterator<BlockVector> nested = BlockSet.this.iterator();
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return nested.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Block next() {
|
||||
BlockVector bv = nested.next();
|
||||
return w.getBlockAt(bv.getBlockX(), bv.getBlockY(), bv.getBlockZ());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tests the two block set overlap each other.
|
||||
* This method works on any implementation of this interface, but they should override the
|
||||
* {@link #overlaps(BlockSet)} method to provide a more optimized code.
|
||||
* @param bs1 the first block set.
|
||||
* @param bs2 the second block set.
|
||||
* @return true if the two block set overlap, false otherwise.
|
||||
*/
|
||||
static boolean overlap(BlockSet bs1, BlockSet bs2) {
|
||||
if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB()))
|
||||
return false;
|
||||
|
@ -69,7 +69,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
/**
|
||||
* Add the hot bar elements to this player, or update them if applicable.
|
||||
*
|
||||
* <br>
|
||||
* The player is automatically removed when they quit. You can remove it before by calling {@link #removePlayer(Player, boolean)}.
|
||||
*/
|
||||
public void addPlayer(Player p) {
|
||||
@ -144,7 +144,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
void onPlayerDropItem(PlayerDropItemEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
@ -159,7 +159,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
@ -188,7 +188,7 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onInventoryClick(InventoryClickEvent event) {
|
||||
void onInventoryClick(InventoryClickEvent event) {
|
||||
if (event.getClickedInventory() == null || !(event.getClickedInventory() instanceof PlayerInventory inv))
|
||||
return;
|
||||
|
||||
@ -213,20 +213,20 @@ public class GUIHotBar implements Listener {
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
void onPlayerQuit(PlayerQuitEvent event) {
|
||||
removePlayer(event.getPlayer());
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
void onPlayerDeath(PlayerDeathEvent event) {
|
||||
if (!currentPlayers.contains(event.getEntity()))
|
||||
return;
|
||||
event.getDrops().removeAll(itemsAndSetters.keySet());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
void onPlayerRespawn(PlayerRespawnEvent event) {
|
||||
if (!currentPlayers.contains(event.getPlayer()))
|
||||
return;
|
||||
|
||||
|
@ -49,7 +49,11 @@ public class GUIInventory implements Listener {
|
||||
Bukkit.getPluginManager().registerEvents(this, PandaLibPaper.getPlugin());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the action when the player closes the inventory window.
|
||||
* @param closeEventAction the action to run.
|
||||
*/
|
||||
protected void setCloseEvent(Consumer<InventoryCloseEvent> closeEventAction) {
|
||||
onCloseEvent = closeEventAction;
|
||||
}
|
||||
@ -188,13 +192,13 @@ public class GUIInventory implements Listener {
|
||||
onCloseEvent.accept(event);
|
||||
isOpened = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Tells the number of inventory line needed to show the provided number of slot.
|
||||
* @param nb the number of slot.
|
||||
* @return the number of line.
|
||||
*/
|
||||
public static int nbLineForNbElements(int nb) {
|
||||
return nb / 9 + ((nb % 9 == 0) ? 0 : 1);
|
||||
}
|
||||
|
@ -12,11 +12,25 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
|
||||
// simplified version of https://github.com/Camotoy/BambooCollisionFix/tree/c7d7d5327791cbb416d106de0b9eb0bf2461acbd/src/main/java/net/camotoy/bamboocollisionfix
|
||||
// we remove the bamboo bounding box due to bedrock clients not having the same placement for bamboos
|
||||
// simplified version of
|
||||
|
||||
/**
|
||||
* Disables the server-side bounding box of the bamboo stalk blocks.
|
||||
* Bamboo stalks are the thin bamboo plants that are shifted differently, depending on the X and Z coordinate.
|
||||
* This X/Z dependent shift is different between Java and Bedrock implementation.
|
||||
* But since this block as a collision box and players cannot go through them, Bedrock players are often rolled back
|
||||
* when they are walking around bamboo stalk due to the server being in Java Edition and thinking the player tries to
|
||||
* move through the bamboo.
|
||||
* To avoid this issue, we reduce to 0 the size of the bounding box on the server.
|
||||
* <br>
|
||||
* See <a href="https://github.com/Camotoy/BambooCollisionFix/tree/c7d7d5327791cbb416d106de0b9eb0bf2461acbd/src/main/java/net/camotoy/bamboocollisionfix">the original implementation</a>.
|
||||
*/
|
||||
public final class BedrockBambooCollisionFixer implements Listener {
|
||||
private final BoundingBox originalBambooBoundingBox = new BoundingBox(6.5D / 16D, 0.0D, 6.5D / 16.0D, 9.5D / 16.0D, 1D, 9.5D / 16.0D);
|
||||
|
||||
/**
|
||||
* Creates a new {@link BedrockBambooCollisionFixer}. There is no need for multiple instances.
|
||||
*/
|
||||
public BedrockBambooCollisionFixer() {
|
||||
// Make the bamboo block have zero collision.
|
||||
try {
|
||||
@ -34,7 +48,7 @@ public final class BedrockBambooCollisionFixer implements Listener {
|
||||
* our ability.
|
||||
*/
|
||||
@EventHandler
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
void onBlockPlace(BlockPlaceEvent event) {
|
||||
if (event.getBlockPlaced().getBlockData().getMaterial().equals(Material.BAMBOO)) {
|
||||
BoundingBox currentBambooBoundingBox = originalBambooBoundingBox.clone().shift(event.getBlockPlaced().getLocation());
|
||||
for (LivingEntity e : event.getBlock().getLocation().getNearbyLivingEntities(5)) {
|
||||
|
@ -5,8 +5,17 @@ import org.bukkit.World;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Represents a XZ chunk coordinates.
|
||||
* @param x the x coordinate.
|
||||
* @param z the z coordinate.
|
||||
*/
|
||||
public record ChunkCoord(int x, int z) {
|
||||
|
||||
/**
|
||||
* Creates the {@link ChunkCoord} of a {@link Chunk}.
|
||||
* @param c the chunks from which to get its coordinates.
|
||||
*/
|
||||
public ChunkCoord(Chunk c) {
|
||||
this(c.getX(), c.getZ());
|
||||
}
|
||||
@ -16,22 +25,48 @@ public record ChunkCoord(int x, int z) {
|
||||
return "(" + x + ";" + z + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the coordinates of the region file.
|
||||
* @return the {@link RegionCoord}.
|
||||
*/
|
||||
public RegionCoord getRegionCoord() {
|
||||
return new RegionCoord(x >> 5, z >> 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Chunk} at this coordinate in the provided World.
|
||||
* @param w the {@link World}.
|
||||
* @return a chunk, using {@link World#getChunkAt(int, int)}.
|
||||
*/
|
||||
public Chunk getChunk(World w) {
|
||||
return w.getChunkAt(x, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Chunk} at this coordinate in the provided World.
|
||||
* @param w the {@link World}.
|
||||
* @param generate Whether the chunk should be fully generated or not.
|
||||
* @return a chunk, using {@link World#getChunkAt(int, int, boolean)}.
|
||||
*/
|
||||
public Chunk getChunk(World w, boolean generate) {
|
||||
return w.getChunkAt(x, z, generate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Chunk} at this coordinate in the provided World, asynchronously.
|
||||
* @param w the {@link World}.
|
||||
* @return a completable future of a chunk, using {@link World#getChunkAtAsync(int, int)}.
|
||||
*/
|
||||
public CompletableFuture<Chunk> getChunkAsync(World w) {
|
||||
return w.getChunkAtAsync(x, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link Chunk} at this coordinate in the provided World, asynchronously.
|
||||
* @param w the {@link World}.
|
||||
* @param generate Whether the chunk should be fully generated or not.
|
||||
* @return a completable future of a chunk, using {@link World#getChunkAtAsync(int, int, boolean)}.
|
||||
*/
|
||||
public CompletableFuture<Chunk> getChunkAsync(World w, boolean generate) {
|
||||
return w.getChunkAtAsync(x, z, generate);
|
||||
}
|
||||
|
@ -3,20 +3,42 @@ package fr.pandacube.lib.paper.world;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a XZ region coordinates.
|
||||
* @param x the x coordinate.
|
||||
* @param z the z coordinate.
|
||||
*/
|
||||
public record RegionCoord(int x, int z) {
|
||||
|
||||
/**
|
||||
* Gets the <a href="https://en.wikipedia.org/wiki/Chebyshev_distance">Chebyshev distance</a> from this region to the center (0, 0) region.
|
||||
* The unit is one region, i.e. the region (1, 0) has a distance of 1 from the center.
|
||||
* @return the distance.
|
||||
*/
|
||||
public int distToCenter() {
|
||||
return Math.max(Math.abs(x), Math.abs(z));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file name that store this region.
|
||||
* @return the region file name.
|
||||
*/
|
||||
public String getFileName() {
|
||||
return "r." + x + "." + z + ".mca";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chunk with the lowest coordinates of this region.
|
||||
* @return the chunk with the lowest coordinates of this region.
|
||||
*/
|
||||
public ChunkCoord getMinChunk() {
|
||||
return new ChunkCoord(x << 5, z << 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the chunk with the highest coordinates of this region.
|
||||
* @return the chunk with the highest coordinates of this region.
|
||||
*/
|
||||
public ChunkCoord getMaxChunk() {
|
||||
return new ChunkCoord(x << 5 | 31, z << 5 | 31);
|
||||
}
|
||||
@ -25,6 +47,12 @@ public record RegionCoord(int x, int z) {
|
||||
|
||||
private static final Pattern REGION_FILE_NAME_PATTERN = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
|
||||
|
||||
/**
|
||||
* Parse the coordinate from a region file name.
|
||||
* @param name the name of the file to parse
|
||||
* @return a new {@link RegionCoord}
|
||||
* @throws IllegalArgumentException if the provided file name is not a valid MCA file name (including the extension).
|
||||
*/
|
||||
public static RegionCoord fromFileName(String name) {
|
||||
Matcher m = REGION_FILE_NAME_PATTERN.matcher(name);
|
||||
if (m.find()) {
|
||||
|
Loading…
Reference in New Issue
Block a user