Compare commits

...

3 Commits

Author SHA1 Message Date
0fcd02c80d Permission system now provides a default player
The default player has a fixed name and uuid that should never collide with existing players.
It will never have self permission data, and it will always be part of the default groups.

It is useful for anonymous permission test (for instance, listing only the servers that are publicly visible for the ping server list)
2023-08-14 01:49:56 +02:00
2d950117d3 New BlockSet super-interface for AABBBlock and AABBBlockGroup + reorganized classes related to geometry to a new package 2023-08-14 00:43:01 +02:00
2f476ce8f2 Relative teleport API in PaperOnlinePlayer 2023-08-14 00:38:05 +02:00
12 changed files with 745 additions and 480 deletions

View File

@ -0,0 +1,218 @@
package fr.pandacube.lib.paper.geometry;
import org.bukkit.Location;
import org.bukkit.util.Vector;
/**
* This vector considers Minecraft X Y Z axis orientation,
* but consider standard (not Minecraft) radian values for yaw and pitch.<br/>
* The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values)
* Is always 1.
*
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI
*
* Pitch :
* Up (+y) = PI/2
* Down (-y) = -PI/2</pre>
*/
public class DirectionalVector {
/**
* The X cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the X (west to east) axis in a Minecraft world.
*/
public final double x;
/**
* The Y cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the Y (bottom to top) axis in a Minecraft world.
*/
public final double y;
/**
* The Z cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the Z (north to south) axis in a Minecraft world.
*/
public final double z;
/**
* The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian.
* It corresponds with Minecraft world as follows :
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI</pre>
*/
public final double yaw;
/**
* The polar angle θ (theta) of this {@link DirectionalVector}, in radian.
* It corresponds with Minecraft world as follows :
* <pre>Pitch :
* Down (-y) = -PI/2
* Up (+y) = PI/2</pre>
*/
public final double pitch;
/**
* Initialize this {@link DirectionalVector} with the yaw and pitch
* 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.
*/
public DirectionalVector(Location l) {
this(
Math.toRadians(((l.getYaw() + 90) % 360) > 180 ? ((l.getYaw() + 90) % 360) - 360 : ((l.getYaw() + 90) % 360)),
-Math.toRadians(l.getPitch())
);
/* MC : +90 : %360 : >180 -> -360
* South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2
* West (-x) = 90 : 180 : 180 : ±180 : ±PI
* North (-z) = 180 : 270 : 270 : -90 : -PI/2
* East (+x) = 270 : 360 : 0-360 : 0 : 0
*/
}
/**
* @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
* {@link #DirectionalVector(Location)}. The {@link Vector} is
* normalized if necessary (does not modify the provided {@link Vector}).
*/
public DirectionalVector(Vector v) {
this(v.getX(), v.getY(), v.getZ());
// this((v = v.clone().normalize()).getX(), v.getY(), v.getZ());
}
private DirectionalVector(double x, double y, double z) {
double vecSize = Math.sqrt(x * x + y * y + z * z);
this.x = x / vecSize;
this.y = y / vecSize;
this.z = z / vecSize;
if (x == 0.0 && z == 0.0) {
pitch = y > 0.0 ? GeometryUtil.PId2 : -GeometryUtil.PId2;
yaw = 0;
} else {
yaw = Math.atan2(z, x);
pitch = Math.atan(y / Math.sqrt(x * x + z * z));
}
}
private DirectionalVector(double x, double y, double z, double yaw, double pitch) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
private DirectionalVector(double yaw, double pitch) {
this.yaw = yaw;
this.pitch = pitch;
y = Math.sin(pitch);
double cosPitch = Math.cos(pitch);
x = cosPitch * Math.cos(yaw);
z = cosPitch * Math.sin(yaw);
}
public Vector toVector() {
return new Vector(x, y, z);
}
/**
* Set the yaw and the pitch of the provided {@link Location}
* with the values inside the current {@link DirectionalVector}
* after conversion of these values
*/
public void putIntoLocation(Location l) {
/* std : -PI/2 : <0 ? +2PI : MC
* South (+z) = PI/2 : 0 : 0 : 0, 360
* West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90
* North (-z) = -PI/2 : -PI : PI : 180
* East (+x) = 0 : -PI/2 : 3PI/2 : 270
*/
l.setYaw((float) Math.toDegrees(yaw < GeometryUtil.PId2 ? yaw + GeometryUtil.PIx2 - GeometryUtil.PId2 : yaw - GeometryUtil.PId2));
l.setPitch((float) Math.toDegrees(-pitch));
}
public DirectionalVector getOpposite() {
return new DirectionalVector(
-x,
-y,
-z,
(yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI)),
-pitch
);
}
/**
* 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()}
*/
public DirectionalVector getBackDirection() {
return getOpposite();
}
/**
* If the current direction is the player face direction,
* this method return the direction of the bottom of the head.
*/
public DirectionalVector getBottomDirection() {
return new DirectionalVector(
(pitch > 0 ? yaw : (yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI))),
(pitch > 0 ? (pitch - GeometryUtil.PId2) : (-GeometryUtil.PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the top of the head.
*/
public DirectionalVector getTopDirection() {
return new DirectionalVector(
(pitch < 0 ? yaw : (yaw > 0 ? (yaw - GeometryUtil.PI) : (yaw + GeometryUtil.PI))),
(pitch < 0 ? (pitch + GeometryUtil.PId2) : (GeometryUtil.PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the left of the head.
*/
public DirectionalVector getLeftDirection() {
return new DirectionalVector(
yaw > -GeometryUtil.PId2 ? (yaw - GeometryUtil.PId2) : (yaw - GeometryUtil.PId2 + GeometryUtil.PIx2),
0
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the right of the head.
*/
public DirectionalVector getRightDirection() {
return new DirectionalVector(
yaw < GeometryUtil.PId2 ? (yaw + GeometryUtil.PId2) : (yaw + GeometryUtil.PId2 - GeometryUtil.PIx2),
0
);
}
}

View File

@ -0,0 +1,155 @@
package fr.pandacube.lib.paper.geometry;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class GeometryUtil {
/**
* Value equal to <code>{@link Math#PI}</code>.
*/
public static final double PI = Math.PI;
/**
* Value equal to <code>{@link Math#PI} / 2</code>.
*/
public static final double PId2 = PI/2;
/**
* Value equal to <code>{@link Math#PI} * 2</code>.
*/
public static final double PIx2 = PI*2;
/*
* Player geometry
*/
/**
* The visual height of a Minecraft player skin, when he is standing up and not sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It doesn't correspond to the player hit box height.<br/>
* <br/>
* The value is provided in Minecraft Wiki.
*/
public static final double PLAYER_SKIN_HEIGHT = 1.85;
/**
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
*/
public static final double PLAYER_EYE_HEIGHT = 1.62;
/**
* The visual height of a Minecraft player skin, when he is standing up and sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It may not correspond to the player hit box height.<br/>
* <br/>
* The current value is the height of the player's hit box when sneaking. Even if this
* is close to the real value (tested in game), this is not the exact value.
*/
public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.50;
/**
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
*/
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27;
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK);
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
/**
* 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).
* @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).
* <pre>
* return[0] // top front left
* return[1] // top front right
* return[2] // bottom front left
* return[3] // bottom front right
* return[4] // top back left
* return[5] // top back right
* return[6] // bottom back left
* return[7] // bottom back right
* </pre>
*/
public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) {
Location[] headAnglesPoints = new Location[8];
Location playerHeadRotationLocation = playerLocation.clone()
.add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0);
DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation);
Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE);
Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector);
Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection);
Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector);
Location topBackMiddle = bottomBackMiddle.clone().add(topVector);
headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector);
headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector);
return headAnglesPoints;
}
/**
* 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>.
*/
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
);
}
}

View File

@ -1,29 +1,26 @@
package fr.pandacube.lib.paper.util;
import java.util.Iterator;
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.entity.Entity;
import org.bukkit.util.BlockVector;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import fr.pandacube.lib.util.RandomUtil;
import java.util.Iterator;
/**
* Checkpoint represented as a 3D Axis and Block Aligned Bounding Box (sort of AABB).
* Block Aligned Bounding Box (sort of AABB).
* Represent the littlest cuboid selection of blocks that contains the bounding box
* passed to the constructor.
*/
public class AABBBlock implements Iterable<BlockVector>, Cloneable {
public final Vector pos1, pos2;
public class AABBBlock implements BlockSet, Cloneable {
/* package */ final Vector pos1, pos2;
private final Vector center;
private final long volume;
private BoundingBox bukkitBoundingBox;
private AABBBlock(AABBBlock original, int shiftX, int shiftY, int shiftZ) {
Vector shiftVec = new Vector(shiftX, shiftY, shiftZ);
@ -37,6 +34,14 @@ public class AABBBlock implements Iterable<BlockVector>, Cloneable {
this(p1.getBlockX(), p1.getBlockY(), p1.getBlockZ(), p2.getBlockX(), p2.getBlockY(), p2.getBlockZ());
}
public AABBBlock(BoundingBox bb) {
pos1 = bb.getMin();
pos2 = bb.getMax();
center = bb.getCenter();
volume = (int) bb.getVolume();
bukkitBoundingBox = bb;
}
public AABBBlock(Location l1, Location l2) {
this(l1.getBlockX(), l1.getBlockY(), l1.getBlockZ(), l2.getBlockX(), l2.getBlockY(), l2.getBlockZ());
}
@ -64,6 +69,11 @@ public class AABBBlock implements Iterable<BlockVector>, Cloneable {
volume = (long) Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_) * Math.abs(p2x_ - p1x_);
}
@Override
public AABBBlock getEnglobingAABB() {
return this;
}
public AABBBlock shift(int x, int y, int z) {
return new AABBBlock(this, x, y, z);
}
@ -74,22 +84,14 @@ public class AABBBlock implements Iterable<BlockVector>, Cloneable {
return new AABBBlock(this, 0, 0, 0);
}
public boolean overlaps(Entity e) {
return overlaps(e.getBoundingBox());
}
public boolean overlaps(BoundingBox bb) {
return asBukkitBoundingBox().overlaps(bb);
}
public boolean isInside(Vector v) {
return v.isInAABB(pos1, pos2);
}
public boolean isInside(Location l) {
return isInside(l.toVector());
}
public boolean isInside(Entity p) {
return isInside(p.getLocation());
return asBukkitBoundingBox().contains(v);
}
public Vector getCenter() {
@ -101,9 +103,12 @@ public class AABBBlock implements Iterable<BlockVector>, Cloneable {
}
public BoundingBox asBukkitBoundingBox() {
return new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
if (bukkitBoundingBox == null) {
bukkitBoundingBox = new BoundingBox(pos1.getX(), pos1.getY(), pos1.getZ(),
pos2.getX(), pos2.getY(), pos2.getZ());
}
return bukkitBoundingBox;
}
public Vector getRandomPosition() {
double x = RandomUtil.rand.nextDouble(pos1.getX(), pos2.getX());
@ -159,4 +164,20 @@ public class AABBBlock implements Iterable<BlockVector>, Cloneable {
}
static boolean overlap(AABBBlock aabb1, AABBBlock aabb2) {
return aabb1.asBukkitBoundingBox().overlaps(aabb2.asBukkitBoundingBox());
}
static boolean overlap(AABBBlock aabb1, BlockSet bs) {
if (!overlap(aabb1, bs.getEnglobingAABB()))
return false;
AABBBlock intersection = new AABBBlock(aabb1.asBukkitBoundingBox().intersection(bs.getEnglobingAABB().asBukkitBoundingBox()));
for (BlockVector bv : intersection)
if (bs.isInside(bv))
return true;
return false;
}
}

View File

@ -0,0 +1,138 @@
package fr.pandacube.lib.paper.geometry.blocks;
import fr.pandacube.lib.util.IteratorIterator;
import fr.pandacube.lib.util.RandomUtil;
import org.bukkit.util.BlockVector;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class AABBBlockGroup implements BlockSet {
public final List<AABBBlock> subAABB;
private final AABBBlock englobingAABB;
public AABBBlockGroup(Collection<AABBBlock> in) {
if (in.isEmpty())
throw new IllegalArgumentException("Provided collection must not be empty.");
subAABB = List.copyOf(in);
englobingAABB = initEnglobingAABB();
}
public AABBBlockGroup(AABBBlock... in) {
this(Arrays.asList(in));
}
private AABBBlock initEnglobingAABB() {
Vector pos1 = subAABB.get(0).pos1.clone();
Vector pos2 = subAABB.get(0).pos2.clone().add(new Vector(-1, -1, -1));
for (int i = 1; i < subAABB.size(); i++) {
AABBBlock aabb = subAABB.get(i);
pos1.setX(Math.min(pos1.getBlockX(), aabb.pos1.getBlockX()));
pos1.setY(Math.min(pos1.getBlockY(), aabb.pos1.getBlockZ()));
pos1.setZ(Math.min(pos1.getBlockY(), aabb.pos1.getBlockZ()));
pos2.setX(Math.max(pos2.getBlockX(), aabb.pos2.getBlockX() - 1));
pos2.setY(Math.max(pos2.getBlockY(), aabb.pos2.getBlockZ() - 1));
pos2.setZ(Math.max(pos2.getBlockY(), aabb.pos2.getBlockZ() - 1));
}
return new AABBBlock(pos1, pos2);
}
@Override
public AABBBlock getEnglobingAABB() {
return englobingAABB;
}
public boolean isInside(Vector v) {
if (!englobingAABB.isInside(v))
return false;
for (AABBBlock b : subAABB)
if (b.isInside(v))
return true;
return false;
}
public Vector getRandomPosition() {
double[] freq = subAABB.stream().mapToDouble(AABBBlock::getVolume).toArray();
int i = RandomUtil.randomIndexOfFrequencies(freq);
return subAABB.get(i).getRandomPosition();
}
public long getVolume() {
long v = 0;
for (AABBBlock b : subAABB)
v += b.getVolume();
return v;
}
@Override
public boolean overlaps(BoundingBox bb) {
if (!englobingAABB.overlaps(bb))
return false;
for (AABBBlock b : subAABB)
if (b.overlaps(bb))
return true;
return false;
}
@Override
public Iterator<BlockVector> iterator() {
return IteratorIterator.ofCollectionOfIterator(subAABB.stream().map(AABBBlock::iterator).toList());
}
/* package */ static boolean overlap(AABBBlockGroup aabbGroup, AABBBlock aabb) {
if (!aabbGroup.englobingAABB.overlaps(aabb))
return false;
for (AABBBlock b : aabbGroup.subAABB)
if (b.overlaps(aabb))
return true;
return false;
}
/* package */ static boolean overlap(AABBBlockGroup aabbGroup1, AABBBlockGroup aabbGroup2) {
if (!aabbGroup1.englobingAABB.overlaps(aabbGroup2.englobingAABB))
return false;
List<AABBBlock> group1SubList = new ArrayList<>();
for (AABBBlock b : aabbGroup1.subAABB) {
if (b.overlaps(aabbGroup2.englobingAABB))
group1SubList.add(b);
}
if (group1SubList.isEmpty())
return false;
List<AABBBlock> group2SubList = new ArrayList<>();
for (AABBBlock b : aabbGroup2.subAABB) {
if (b.overlaps(aabbGroup1.englobingAABB))
group2SubList.add(b);
}
if (group2SubList.isEmpty())
return false;
for (AABBBlock b1 : group1SubList)
for (AABBBlock b2 : group2SubList)
if (b1.overlaps(b2))
return true;
return false;
}
static boolean overlap(AABBBlockGroup aabbGroup, BlockSet bs) {
if (!aabbGroup.englobingAABB.overlaps(bs.getEnglobingAABB()))
return false;
for (AABBBlock b : aabbGroup.subAABB) {
if (b.overlaps(bs)) // already checks for englobingAABB before checking block per block
return true;
}
return false;
}
}

View File

@ -0,0 +1,64 @@
package fr.pandacube.lib.paper.geometry.blocks;
import org.bukkit.Location;
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;
public interface BlockSet extends Iterable<BlockVector> {
Vector getRandomPosition();
long getVolume();
AABBBlock getEnglobingAABB();
boolean overlaps(BoundingBox bb);
default boolean overlaps(Entity e) {
return overlaps(e.getBoundingBox());
}
default boolean overlaps(BlockSet bs) {
if (this instanceof AABBBlock b1) {
if (bs instanceof AABBBlock b2)
return AABBBlock.overlap(b1, b2);
if (bs instanceof AABBBlockGroup bg2)
return AABBBlockGroup.overlap(bg2, b1);
return AABBBlock.overlap(b1, bs);
}
if (this instanceof AABBBlockGroup bg1) {
if (bs instanceof AABBBlock b2)
return AABBBlockGroup.overlap(bg1, b2);
if (bs instanceof AABBBlockGroup bg2)
return AABBBlockGroup.overlap(bg1, bg2);
return AABBBlockGroup.overlap(bg1, bs);
}
return overlap(this, bs);
}
boolean isInside(Vector v);
default boolean isInside(Location l) {
return isInside(l.toVector());
}
default boolean isInside(Block b) {
return isInside(b.getLocation().add(.5, .5, .5));
}
default boolean isInside(Entity p) {
return isInside(p.getLocation());
}
static boolean overlap(BlockSet bs1, BlockSet bs2) {
if (!bs1.getEnglobingAABB().overlaps(bs2.getEnglobingAABB()))
return false;
AABBBlock intersection = new AABBBlock(bs1.getEnglobingAABB().asBukkitBoundingBox().intersection(bs2.getEnglobingAABB().asBukkitBoundingBox()));
for (BlockVector bv : intersection)
if (bs1.isInside(bv) && bs2.isInside(bv))
return true;
return false;
}
}

View File

@ -7,6 +7,7 @@ import fr.pandacube.lib.paper.players.PlayerNonPersistentConfig.Expiration;
import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftPlayer;
import fr.pandacube.lib.players.standalone.AbstractOnlinePlayer;
import fr.pandacube.lib.reflect.wrapper.ReflectWrapper;
import io.papermc.paper.entity.TeleportFlag.Relative;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.title.Title.Times;
@ -294,6 +295,35 @@ public interface PaperOnlinePlayer extends PaperOffPlayer, AbstractOnlinePlayer
/*
* Relative teleport
*/
/**
* Teleports this player to the specified relative location, using the {@link Relative} flags.
* @param relX the relative x coordinate.
* @param relY the relative y coordinate.
* @param relZ the relative z coordinate.
*/
@SuppressWarnings("UnstableApiUsage")
default void teleportRelatively(float relX, float relY, float relZ) {
getBukkitPlayer().teleport(getBukkitPlayer().getLocation().add(relX, relY, relZ), Relative.X, Relative.Y, Relative.Z, Relative.YAW, Relative.PITCH);
}
/**
* Teleports this player to the specified location, using the {@link Relative} flags.
* @param destination the destination.
*/
@SuppressWarnings("UnstableApiUsage")
default void teleportRelatively(Location destination) {
getBukkitPlayer().teleport(destination, Relative.X, Relative.Y, Relative.Z, Relative.YAW, Relative.PITCH);
}
/*
* Player config

View File

@ -1,59 +0,0 @@
package fr.pandacube.lib.paper.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
import fr.pandacube.lib.util.IteratorIterator;
import fr.pandacube.lib.util.RandomUtil;
public class AABBBlockGroup implements Iterable<BlockVector> {
public final List<AABBBlock> aabbBlocks;
public AABBBlockGroup(Collection<AABBBlock> in) {
aabbBlocks = List.copyOf(in);
}
public AABBBlockGroup(AABBBlock... in) {
aabbBlocks = List.of(in);
}
public boolean isInside(Vector v) {
for (AABBBlock b : aabbBlocks)
if (b.isInside(v))
return true;
return false;
}
public boolean isInside(Location l) {
return isInside(l.toVector());
}
public boolean isInside(Entity p) {
return isInside(p.getLocation());
}
public Vector getRandomPosition() {
double[] freq = aabbBlocks.stream().mapToDouble(AABBBlock::getVolume).toArray();
int i = RandomUtil.randomIndexOfFrequencies(freq);
return aabbBlocks.get(i).getRandomPosition();
}
public long getVolume() {
long v = 0;
for (AABBBlock b : aabbBlocks)
v += b.getVolume();
return v;
}
@Override
public Iterator<BlockVector> iterator() {
return IteratorIterator.ofCollectionOfIterator(aabbBlocks.stream().map(AABBBlock::iterator).toList());
}
}

View File

@ -1,395 +0,0 @@
package fr.pandacube.lib.paper.util;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.util.Vector;
public class GeometryUtil {
/**
* Value equal to <code>{@link Math#PI}</code>.
*/
public static final double PI = Math.PI;
/**
* Value equal to <code>{@link Math#PI} / 2</code>.
*/
public static final double PId2 = PI/2;
/**
* Value equal to <code>{@link Math#PI} * 2</code>.
*/
public static final double PIx2 = PI*2;
/*
* Player geometry
*/
/**
* The visual height of a Minecraft player skin, when he is standing up and not sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It doesn't correspond to the player hit box height.<br/>
* <br/>
* The value is provided in Minecraft Wiki.
*/
public static final double PLAYER_SKIN_HEIGHT = 1.85;
/**
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
*/
public static final double PLAYER_EYE_HEIGHT = 1.62;
/**
* The visual height of a Minecraft player skin, when he is standing up and sneaking,
* from the ground where the player is standing on, to the above of the first layer of the head skin.
* It may not correspond to the player hit box height.<br/>
* <br/>
* The current value is the height of the player's hit box when sneaking. Even if this
* is close to the real value (tested in game), this is not the exact value.
*/
public static final double PLAYER_SKIN_HEIGHT_SNEAK = 1.50;
/**
* Value provided by net.minecraft.world.entity.player.Player#getStandingEyeHeight
*/
public static final double PLAYER_EYE_HEIGHT_SNEAK = 1.27;
public static final double PLAYER_SKIN_PIXEL_SIZE = PLAYER_SKIN_HEIGHT / 32;
public static final double PLAYER_HEAD_ROTATION_HEIGHT = PLAYER_SKIN_PIXEL_SIZE * 24;
public static final double PLAYER_HEAD_ROTATION_HEIGHT_SNEAK = PLAYER_HEAD_ROTATION_HEIGHT - (PLAYER_SKIN_HEIGHT - PLAYER_SKIN_HEIGHT_SNEAK);
public static final double PLAYER_HEAD_SIZE = PLAYER_SKIN_PIXEL_SIZE * 8;
/**
* 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).
* @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).
* <pre>
* return[0] // top front left
* return[1] // top front right
* return[2] // bottom front left
* return[3] // bottom front right
* return[4] // top back left
* return[5] // top back right
* return[6] // bottom back left
* return[7] // bottom back right
* </pre>
*/
public static Location[] getPlayerHeadGeometry(Location playerLocation, boolean isSneaking) {
Location[] headAnglesPoints = new Location[8];
Location playerHeadRotationLocation = playerLocation.clone()
.add(0, isSneaking ? PLAYER_HEAD_ROTATION_HEIGHT_SNEAK : PLAYER_HEAD_ROTATION_HEIGHT, 0);
DirectionalVector frontDirection = new DirectionalVector(playerHeadRotationLocation);
Vector frontHalfVector = frontDirection.toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector backHalfDirection = frontDirection.getBackDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector leftHalfVector = frontDirection.getLeftDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector rightHalfVector = frontDirection.getRightDirection().toVector().multiply(PLAYER_HEAD_SIZE/2);
Vector topVector = frontDirection.getTopDirection().toVector().multiply(PLAYER_HEAD_SIZE);
Location bottomFrontMiddle = playerHeadRotationLocation.clone().add(frontHalfVector);
Location bottomBackMiddle = playerHeadRotationLocation.clone().add(backHalfDirection);
Location topFrontMiddle = bottomFrontMiddle.clone().add(topVector);
Location topBackMiddle = bottomBackMiddle.clone().add(topVector);
headAnglesPoints[0] = topFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[1] = topFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[2] = bottomFrontMiddle.clone().add(leftHalfVector);
headAnglesPoints[3] = bottomFrontMiddle.clone().add(rightHalfVector);
headAnglesPoints[4] = topBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[5] = topBackMiddle.clone().add(rightHalfVector);
headAnglesPoints[6] = bottomBackMiddle.clone().add(leftHalfVector);
headAnglesPoints[7] = bottomBackMiddle.clone().add(rightHalfVector);
return headAnglesPoints;
}
/**
* 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>.
*/
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
);
}
/**
* This vector considers Minecraft X Y Z axis orientation,
* but consider standard (not Minecraft) radian values for yaw and pitch.<br/>
* The length of this Vector (based on {@link #x}, {@link #y} and {@link #z} values)
* Is always 1.
*
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI
*
* Pitch :
* Up (+y) = PI/2
* Down (-y) = -PI/2</pre>
*/
public static class DirectionalVector {
/**
* The X cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the X (west to east) axis in a Minecraft world.
*/
public final double x;
/**
* The Y cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the Y (bottom to top) axis in a Minecraft world.
*/
public final double y;
/**
* The Z cartesian coordinate of this {@link DirectionalVector}.
* It corresponds to the Z (north to south) axis in a Minecraft world.
*/
public final double z;
/**
* The azimuthal angle φ (phi) of this {@link DirectionalVector}, in radian.
* It corresponds with Minecraft world as follows :
* <pre>Yaw :
* North (-z) = -PI/2
* East (+x) = 0
* South (+z) = PI/2
* West (-x) = ±PI</pre>
*/
public final double yaw;
/**
* The polar angle θ (theta) of this {@link DirectionalVector}, in radian.
* It corresponds with Minecraft world as follows :
* <pre>Pitch :
* Down (-y) = -PI/2
* Up (+y) = PI/2</pre>
*/
public final double pitch;
/**
* Initialize this {@link DirectionalVector} with the yaw and pitch
* 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.
*/
public DirectionalVector(Location l) {
this(
Math.toRadians(((l.getYaw()+90)%360) > 180 ? ((l.getYaw()+90)%360)-360 : ((l.getYaw()+90)%360)),
-Math.toRadians(l.getPitch())
);
/* MC : +90 : %360 : >180 -> -360
* South (+z) = 0, 360 : 90-450 : 90 : 90 : PI/2
* West (-x) = 90 : 180 : 180 : ±180 : ±PI
* North (-z) = 180 : 270 : 270 : -90 : -PI/2
* East (+x) = 270 : 360 : 0-360 : 0 : 0
*/
}
/**
* @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
* {@link #DirectionalVector(Location)}. The {@link Vector} is
* normalized if necessary (does not modify the provided {@link Vector}).
*/
public DirectionalVector(Vector v) {
this(v.getX(), v.getY(), v.getZ());
// this((v = v.clone().normalize()).getX(), v.getY(), v.getZ());
}
private DirectionalVector(double x, double y, double z) {
double vecSize = Math.sqrt(x*x + y*y + z*z);
this.x = x/vecSize;
this.y = y/vecSize;
this.z = z/vecSize;
if (x == 0.0 && z == 0.0) {
pitch = y > 0.0 ? PId2 : -PId2;
yaw = 0;
}
else {
yaw = Math.atan2(z, x);
pitch = Math.atan(y / Math.sqrt(x*x + z*z));
}
}
private DirectionalVector(double x, double y, double z, double yaw, double pitch) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
}
private DirectionalVector(double yaw, double pitch) {
this.yaw = yaw;
this.pitch = pitch;
y = Math.sin(pitch);
double cosPitch = Math.cos(pitch);
x = cosPitch * Math.cos(yaw);
z = cosPitch * Math.sin(yaw);
}
public Vector toVector() {
return new Vector(x, y, z);
}
/**
* Set the yaw and the pitch of the provided {@link Location}
* with the values inside the current {@link DirectionalVector}
* after conversion of these values
*/
public void putIntoLocation(Location l) {
/* std : -PI/2 : <0 ? +2PI : MC
* South (+z) = PI/2 : 0 : 0 : 0, 360
* West (-x) = ±PI : -3PI/2 - PI/2 : PI/2 : 90
* North (-z) = -PI/2 : -PI : PI : 180
* East (+x) = 0 : -PI/2 : 3PI/2 : 270
*/
l.setYaw((float)Math.toDegrees(yaw < PId2 ? yaw + PIx2 - PId2 : yaw - PId2));
l.setPitch((float)Math.toDegrees(-pitch));
}
public DirectionalVector getOpposite() {
return new DirectionalVector(
-x,
-y,
-z,
(yaw > 0 ? (yaw - PI) : (yaw + PI)),
-pitch
);
}
/**
* 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()}
*/
public DirectionalVector getBackDirection() {
return getOpposite();
}
/**
* If the current direction is the player face direction,
* this method return the direction of the bottom of the head.
*/
public DirectionalVector getBottomDirection() {
return new DirectionalVector(
(pitch > 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))),
(pitch > 0 ? (pitch - PId2) : (-PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the top of the head.
*/
public DirectionalVector getTopDirection() {
return new DirectionalVector(
(pitch < 0 ? yaw : (yaw > 0 ? (yaw - PI) : (yaw + PI))),
(pitch < 0 ? (pitch + PId2) : (PId2 - pitch))
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the left of the head.
*/
public DirectionalVector getLeftDirection() {
return new DirectionalVector(
yaw > -PId2 ? (yaw - PId2) : (yaw - PId2 + PIx2),
0
);
}
/**
* If the current direction is the player face direction,
* this method return the direction of the right of the head.
*/
public DirectionalVector getRightDirection() {
return new DirectionalVector(
yaw < PId2 ? (yaw + PId2) : (yaw + PId2 - PIx2),
0
);
}
}
}

View File

@ -0,0 +1,70 @@
package fr.pandacube.lib.permissions;
import fr.pandacube.lib.util.Log;
import java.util.UUID;
/**
* Represents a dummy player in the permission system, that have no specific data, only inheriting from the default
* groups.
*
* The current implementation provides a player named {@code default.0} with an uuid of
* {@code fffdef17-ffff-b0ff-ffff-ffffffffffff}.
* Trying to set a permission data for this player will log a warning.
*/
/* package */ final class DefaultPlayer extends PermPlayer {
// a static UUID that ensure it will not collide with any player, either online or offline or floodgate:
// the version bits are set to B (11), that is offline mode (3) + alt (8) account, and alt account counter
// set to 0 that is usually impossible due to the counter starting at 1.
/* package */ static final UUID ID = new UUID(0xfffdef17_ffff_b0ffL, -1L);
/* package */ static final String NAME = "default.0";
/* package */ DefaultPlayer() {
super(ID);
}
@Override
public String getName() {
return NAME;
}
public void setGroup(String group) {
warnDefaultPlayerSetData();
}
public void addGroup(String group) {
warnDefaultPlayerSetData();
}
public void removeGroup(String group) {
warnDefaultPlayerSetData();
}
@Override
public void setSelfPrefix(String prefix) {
warnDefaultPlayerSetData();
}
@Override
public void setSelfSuffix(String suffix) {
warnDefaultPlayerSetData();
}
@Override
public void addSelfPermission(String permission, String server, String world) {
warnDefaultPlayerSetData();
}
@Override
public void removeSelfPermission(String permission, String server, String world) {
warnDefaultPlayerSetData();
}
private void warnDefaultPlayerSetData() {
Log.warning(new UnsupportedOperationException("Trying to set permission data of default player"));
}
}

View File

@ -10,7 +10,7 @@ import fr.pandacube.lib.permissions.SQLPermissions.EntityType;
/**
* Represents a player in the permission system.
*/
public final class PermPlayer extends PermEntity {
public sealed class PermPlayer extends PermEntity permits DefaultPlayer {
private final UUID playerId;
/* package */ PermPlayer(UUID id) {
super(id.toString(), EntityType.User);

View File

@ -104,6 +104,19 @@ public class Permissions {
return new PermPlayer(playerId);
}
/**
* Gets a dummy permission player object, that have no specific data, only inheriting from the default groups.
*
* The current implementation provides a player named {@code default.0} with an uuid of
* {@code fffdef17-ffff-b0ff-ffff-ffffffffffff}.
* Trying to set a permission data for this player will log a warning.
* @return the default permission player.
*/
public static PermPlayer getDefaultPlayer() {
checkInitialized();
return new DefaultPlayer();
}
/**
* Asks the permission system to preventively and asynchronously cache the data of the provided player.
* This can be called as soon as possible when a player connects, so the permission data of the player are

View File

@ -28,6 +28,12 @@ import fr.pandacube.lib.util.Log;
}
/* package */ static final CachedPlayer DEFAULT_PLAYER = new CachedPlayer(DefaultPlayer.ID, null, null, Map.of());
static {
DEFAULT_PLAYER.usingDefaultGroups = true;
}
private final Cache<UUID, CachedPlayer> usersCache = CacheBuilder.newBuilder()
@ -58,6 +64,8 @@ import fr.pandacube.lib.util.Log;
}
private CachedPlayer initPlayer(UUID playerId) throws DBException {
if (playerId.equals(DEFAULT_PLAYER.playerId))
return DEFAULT_PLAYER;
SQLElementList<SQLPermissions> playerData = DB.getAll(SQLPermissions.class,
SQLPermissions.type.eq(EntityType.User.getCode())
@ -196,6 +204,8 @@ import fr.pandacube.lib.util.Log;
cacheIsUpdating = false;
usersCache.invalidateAll();
fullPermissionsList = newFullPermissionsList;
DEFAULT_PLAYER.groups.clear();
DEFAULT_PLAYER.groups.addAll(getDefaultGroups());
}
} finally {
synchronized (this) {