More complete Javadoc

This commit is contained in:
Marc Baloup 2024-12-22 23:44:46 +01:00
parent 3d92c3afb6
commit 8f5f880754
14 changed files with 517 additions and 94 deletions

View File

@ -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 {

View File

@ -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));
}
}

View File

@ -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<>();
}

View File

@ -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);

View File

@ -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(

View File

@ -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() {}
}

View File

@ -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

View File

@ -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));
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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)) {

View File

@ -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);
}

View File

@ -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()) {