Add posturing to position playback
This commit is contained in:
@@ -34,6 +34,7 @@ public class APPlayer {
|
||||
// hotbar, main, armor, offhand, echest
|
||||
private List<ItemStack> invDiffItems;
|
||||
private Location lastLocationDiff;
|
||||
private PosEncoder.Posture lastPosture;
|
||||
|
||||
public APPlayer(IAuxProtect plugin, Player player) {
|
||||
this.player = player;
|
||||
@@ -144,9 +145,11 @@ public class APPlayer {
|
||||
public void tickDiffPos() {
|
||||
if (lastLocationDiff != null) {
|
||||
synchronized (posBlob) {
|
||||
for (byte b : PosEncoder.encode(lastLocationDiff, player.getLocation())) {
|
||||
PosEncoder.Posture posture = PosEncoder.Posture.fromPlayer(player);
|
||||
for (byte b : PosEncoder.encode(lastLocationDiff, player.getLocation(), posture, lastPosture)) {
|
||||
posBlob.add(b);
|
||||
}
|
||||
lastPosture = posture;
|
||||
}
|
||||
}
|
||||
lastLocationDiff = player.getLocation().clone();
|
||||
|
||||
@@ -425,6 +425,8 @@ public class LookupCommand extends Command {
|
||||
sender.sendMessageRaw(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
sender.sendLang(Language.L.ERROR);
|
||||
plugin.warning("Error during lookup:");
|
||||
plugin.print(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -372,18 +372,16 @@ public class ConnectionPool {
|
||||
}
|
||||
}
|
||||
|
||||
protected int count(String table) throws SQLException {
|
||||
protected int count(Connection connection, String table) throws SQLException {
|
||||
String stmtStr = getCountStmt(table);
|
||||
plugin.debug(stmtStr, 5);
|
||||
|
||||
return executeReturn(connection -> {
|
||||
try (PreparedStatement pstmt = connection.prepareStatement(stmtStr)) {
|
||||
try (ResultSet rs = pstmt.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
return -1;
|
||||
}
|
||||
try (PreparedStatement pstmt = connection.prepareStatement(stmtStr)) {
|
||||
try (ResultSet rs = pstmt.executeQuery()) {
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
return -1;
|
||||
}
|
||||
}, 30000L, Integer.class);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getCountStmt(String table) {
|
||||
|
||||
@@ -54,7 +54,7 @@ public class MigrationManager {
|
||||
plugin.info("Tables renamed");
|
||||
}, () -> {
|
||||
for (Table table : migrateTablesV3[0]) {
|
||||
total += sql.count(table + "_temp");
|
||||
total += sql.count(connection, table + "_temp");
|
||||
}
|
||||
if (plugin.getPlatform() == PlatformType.BUNGEE) {
|
||||
migrateTablesV3[0] = new Table[]{Table.AUXPROTECT_MAIN, Table.AUXPROTECT_LONGTERM};
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PosEntry extends DbEntry {
|
||||
|
||||
protected PosEntry(long time, int uid, EntryAction action, boolean state, String world, int x, int y, int z, byte increment, int pitch, int yaw, String target, int target_id, String data) {
|
||||
super(time, uid, action, state, world, x, y, z, pitch, yaw, target, target_id, data);
|
||||
double[] dInc = PosEncoder.byteToFractions(increment);
|
||||
double[] dInc = byteToFractions(increment);
|
||||
this.x = x + dInc[0];
|
||||
this.y = y + dInc[1];
|
||||
this.z = z + dInc[2];
|
||||
@@ -36,6 +36,38 @@ public class PosEntry extends DbEntry {
|
||||
this.z = location.getZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fraction of the x/y/z values into a single byte. The structure is as follows
|
||||
* 0b X X X Y Y Z Z Z
|
||||
* X and Z are stored in 8ths, Y is stored in 4ths.
|
||||
*/
|
||||
public static byte getFractionalByte(double dx, double dy, double dz) {
|
||||
dx %= 1;
|
||||
dy %= 1;
|
||||
dz %= 1;
|
||||
if (dx < 0) dx++;
|
||||
if (dy < 0) dy++;
|
||||
if (dz < 0) dz++;
|
||||
int x = (int) Math.min(Math.round(dx * 8), 7) << 5;
|
||||
int y = (int) Math.min(Math.round(dy * 4), 3) << 3;
|
||||
int z = (int) Math.min(Math.round(dz * 8), 7);
|
||||
|
||||
return (byte) (x | y | z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fractional values from the increment byte generated in {@link PosEncoder#getFractionalByte(double, double, double)}
|
||||
*
|
||||
* @return An array of doubles of length 3, containing the x, y, and z fractions respectively.
|
||||
*/
|
||||
public static double[] byteToFractions(byte b) {
|
||||
int x = (b >> 5) & 0b111;
|
||||
int y = (b >> 3) & 0b11;
|
||||
int z = b & 0b111;
|
||||
|
||||
return new double[]{x / 8D, y / 4D, z / 8D};
|
||||
}
|
||||
|
||||
public double getDoubleX() {
|
||||
return x;
|
||||
}
|
||||
@@ -64,6 +96,6 @@ public class PosEntry extends DbEntry {
|
||||
}
|
||||
|
||||
public byte getIncrement() {
|
||||
return PosEncoder.getFractionalByte(x, y, z);
|
||||
return getFractionalByte(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,9 @@ public class SQLManager extends ConnectionPool {
|
||||
invblobmanager.init(connection);
|
||||
}
|
||||
|
||||
if (getLast(LastKeys.LEGACY_POSITIONS, connection) == 0)
|
||||
setLast(LastKeys.LEGACY_POSITIONS, System.currentTimeMillis(), connection);
|
||||
|
||||
plugin.debug("init done.");
|
||||
|
||||
}
|
||||
@@ -570,7 +573,7 @@ public class SQLManager extends ConnectionPool {
|
||||
}
|
||||
|
||||
public int count(Table table) throws SQLException {
|
||||
return count(table.toString());
|
||||
return executeReturn(connection -> count(connection, table.toString()), 30000L, Integer.class);
|
||||
}
|
||||
|
||||
public byte[] getBlob(DbEntry entry) throws SQLException {
|
||||
@@ -629,6 +632,7 @@ public class SQLManager extends ConnectionPool {
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (!isConnected()) return;
|
||||
try {
|
||||
execute(connection -> {
|
||||
Arrays.asList(Table.values()).forEach(t -> {
|
||||
@@ -652,18 +656,22 @@ public class SQLManager extends ConnectionPool {
|
||||
|
||||
//TODO implement
|
||||
public void setLast(LastKeys key, long value) throws SQLException {
|
||||
key.value = value;
|
||||
execute(connection -> setLast(key, value, connection), 30000L);
|
||||
}
|
||||
|
||||
public void setLast(LastKeys key, long value, Connection connection) throws SQLException {
|
||||
key.value = value;
|
||||
execute("UPDATE " + Table.AUXPROTECT_LASTS + " SET value=? WHERE name=?", connection, value, key.id);
|
||||
}
|
||||
|
||||
public long getLast(LastKeys key) throws SQLException {
|
||||
if (key.value != null) return key.value;
|
||||
return executeReturn(connection -> getLast(key, connection), 30000L, Long.class);
|
||||
}
|
||||
|
||||
public long getLast(LastKeys key, Connection connection) throws SQLException {
|
||||
if (key.value != null) return key.value;
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT value FROM " + Table.AUXPROTECT_LASTS + " WHERE name=?")) {
|
||||
stmt.setShort(1, key.id);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
@@ -681,9 +689,10 @@ public class SQLManager extends ConnectionPool {
|
||||
|
||||
|
||||
public enum LastKeys {
|
||||
AUTO_PURGE(1), VACUUM(2), TELEMETRY(3);
|
||||
AUTO_PURGE(1), VACUUM(2), TELEMETRY(3), LEGACY_POSITIONS(4);
|
||||
|
||||
public final short id;
|
||||
private Long value;
|
||||
|
||||
LastKeys(int id) {
|
||||
this.id = (short) id;
|
||||
|
||||
@@ -10,10 +10,7 @@ import dev.heliosares.auxprotect.database.*;
|
||||
import dev.heliosares.auxprotect.exceptions.BusyException;
|
||||
import dev.heliosares.auxprotect.spigot.listeners.*;
|
||||
import dev.heliosares.auxprotect.towny.TownyListener;
|
||||
import dev.heliosares.auxprotect.utils.Pane;
|
||||
import dev.heliosares.auxprotect.utils.StackUtil;
|
||||
import dev.heliosares.auxprotect.utils.Telemetry;
|
||||
import dev.heliosares.auxprotect.utils.UpdateChecker;
|
||||
import dev.heliosares.auxprotect.utils.*;
|
||||
import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
@@ -224,6 +221,7 @@ public class AuxProtectSpigot extends JavaPlugin implements IAuxProtect {
|
||||
3);
|
||||
delay = (1000 * 60 * 60 - (System.currentTimeMillis() - lastloaded)) / 50;
|
||||
}
|
||||
|
||||
getServer().getScheduler().runTaskLater(AuxProtectSpigot.this, () -> Telemetry.init(AuxProtectSpigot.this, 14232), delay);
|
||||
|
||||
/*
|
||||
@@ -626,6 +624,7 @@ public class AuxProtectSpigot extends JavaPlugin implements IAuxProtect {
|
||||
sqlManager = null;
|
||||
}
|
||||
Pane.shutdown();
|
||||
PlaybackSolver.shutdown();
|
||||
info("Done disabling.");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.wrappers.*;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
@@ -18,6 +21,7 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FakePlayer {
|
||||
@@ -26,10 +30,9 @@ public class FakePlayer {
|
||||
private final String name;
|
||||
private final ProtocolManager protocol;
|
||||
private final Player audience;
|
||||
|
||||
private Location loc;
|
||||
|
||||
private long lastMoved;
|
||||
private PosEncoder.Posture currentPosture = PosEncoder.Posture.STANDING;
|
||||
|
||||
public FakePlayer(String name, ProtocolManager protocol, Player audience) {
|
||||
this.uuid = generateNPCUUID();
|
||||
@@ -62,7 +65,6 @@ public class FakePlayer {
|
||||
|
||||
public void spawn(Location loc_, @Nullable Skin skin) {
|
||||
this.loc = loc_;
|
||||
|
||||
// Sends player info, creates the player
|
||||
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.PLAYER_INFO);
|
||||
@@ -81,7 +83,7 @@ public class FakePlayer {
|
||||
// Set initial location
|
||||
|
||||
packet = new PacketContainer(PacketType.Play.Server.NAMED_ENTITY_SPAWN);
|
||||
packet.getIntegers().write(0, id);
|
||||
setIdInPacket(packet);
|
||||
packet.getUUIDs().write(0, uuid);
|
||||
packet.getDoubles().write(0, loc.getX());
|
||||
packet.getDoubles().write(1, loc.getY());
|
||||
@@ -91,23 +93,23 @@ public class FakePlayer {
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
}
|
||||
|
||||
public void setLocation(Location loc) {
|
||||
|
||||
public void setLocation(Location loc, boolean onGround) {
|
||||
// Move entity
|
||||
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.REL_ENTITY_MOVE_LOOK);
|
||||
packet.getIntegers().write(0, id);
|
||||
setIdInPacket(packet);
|
||||
packet.getShorts().write(0, (short) ((loc.getX() - this.loc.getX()) * 4096));
|
||||
packet.getShorts().write(1, (short) ((loc.getY() - this.loc.getY()) * 4096));
|
||||
packet.getShorts().write(2, (short) ((loc.getZ() - this.loc.getZ()) * 4096));
|
||||
packet.getBytes().write(0, (byte) (loc.getYaw() * 256f / 360f));
|
||||
packet.getBytes().write(1, (byte) (loc.getPitch() * 256f / 360f));
|
||||
packet.getBooleans().write(0, onGround);
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
|
||||
// Update head
|
||||
|
||||
packet = new PacketContainer(PacketType.Play.Server.ENTITY_HEAD_ROTATION);
|
||||
packet.getIntegers().write(0, id);
|
||||
setIdInPacket(packet);
|
||||
packet.getBytes().write(0, (byte) (loc.getYaw() * 256f / 360f));
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
|
||||
@@ -115,8 +117,51 @@ public class FakePlayer {
|
||||
this.loc = loc;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
public void setPosture(PosEncoder.Posture posture) {
|
||||
if (currentPosture == posture) return;
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
|
||||
|
||||
setIdInPacket(packet);
|
||||
|
||||
final List<WrappedDataValue> wrappedDataValueList = Lists.newArrayList();
|
||||
wrappedDataValueList.add(new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte) switch (posture) {
|
||||
case STANDING, SITTING, SLEEPING -> 0;
|
||||
case SNEAKING -> 0x02;
|
||||
case SWIMMING, CRAWLING -> 0x10;
|
||||
case GLIDING -> 0x80;
|
||||
}));
|
||||
wrappedDataValueList.add(new WrappedDataValue(6, WrappedDataWatcher.Registry.get(EnumWrappers.getEntityPoseClass()), switch (posture) {
|
||||
case STANDING -> EnumWrappers.EntityPose.STANDING;
|
||||
case SNEAKING -> EnumWrappers.EntityPose.CROUCHING;
|
||||
case SWIMMING, CRAWLING -> EnumWrappers.EntityPose.SWIMMING;
|
||||
case GLIDING -> EnumWrappers.EntityPose.FALL_FLYING;
|
||||
case SITTING -> EnumWrappers.EntityPose.SITTING;
|
||||
case SLEEPING -> EnumWrappers.EntityPose.SLEEPING;
|
||||
}));
|
||||
packet.getDataValueCollectionModifier().write(0, wrappedDataValueList);
|
||||
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
|
||||
if (posture == PosEncoder.Posture.GLIDING) {
|
||||
setEquipment(EnumWrappers.ItemSlot.CHEST, new ItemStack(Material.ELYTRA));
|
||||
} else if (currentPosture == PosEncoder.Posture.GLIDING) {
|
||||
setEquipment(EnumWrappers.ItemSlot.CHEST, new ItemStack(Material.AIR));
|
||||
}
|
||||
|
||||
currentPosture = posture;
|
||||
}
|
||||
|
||||
public void setEquipment(EnumWrappers.ItemSlot slot, ItemStack item) {
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.ENTITY_EQUIPMENT);
|
||||
|
||||
setIdInPacket(packet);
|
||||
|
||||
packet.getSlotStackPairLists().write(0, List.of(new Pair<>(slot,item)));
|
||||
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
// Removes player info
|
||||
|
||||
PacketContainer packet = new PacketContainer(PacketType.Play.Server.PLAYER_INFO_REMOVE);
|
||||
@@ -136,6 +181,10 @@ public class FakePlayer {
|
||||
protocol.sendServerPacket(audience, packet);
|
||||
}
|
||||
|
||||
private void setIdInPacket(PacketContainer packet) {
|
||||
packet.getIntegers().write(0, id);
|
||||
}
|
||||
|
||||
public long getLastMoved() {
|
||||
return lastMoved;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import dev.heliosares.auxprotect.core.Language;
|
||||
import dev.heliosares.auxprotect.core.PlatformType;
|
||||
import dev.heliosares.auxprotect.database.DbEntry;
|
||||
import dev.heliosares.auxprotect.database.DbEntryBukkit;
|
||||
import dev.heliosares.auxprotect.database.SQLManager;
|
||||
import dev.heliosares.auxprotect.exceptions.LookupException;
|
||||
import dev.heliosares.auxprotect.spigot.AuxProtectSpigot;
|
||||
import net.md_5.bungee.api.ChatMessageType;
|
||||
@@ -17,6 +18,7 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
@@ -88,45 +90,10 @@ public class PlaybackSolver extends BukkitRunnable {
|
||||
runTaskTimer((AuxProtectSpigot) plugin, 1, 1);
|
||||
}
|
||||
|
||||
public static List<PosPoint> getLocations(IAuxProtect plugin, List<DbEntry> entries, long startTime) throws SQLException {
|
||||
if (plugin.getPlatform() != PlatformType.SPIGOT) throw new UnsupportedOperationException();
|
||||
Map<String, DbEntry> lastEntries = new HashMap<>();
|
||||
entries.sort(Comparator.comparingLong(DbEntry::getTime));
|
||||
List<PosPoint> points = new ArrayList<>();
|
||||
for (DbEntry entry : entries) {
|
||||
DbEntry lastEntry = lastEntries.get(entry.getUser());
|
||||
if (lastEntry != null && entry.getBlob() != null) {
|
||||
List<PosEncoder.DecodedPositionIncrement> decoded = PosEncoder.decode(entry.getBlob());
|
||||
Location lastLoc = DbEntryBukkit.getLocation(lastEntry);
|
||||
final long incrementBy = (entry.getTime() - lastEntry.getTime()) / (decoded.size() + 1);
|
||||
for (int i = 0; i < decoded.size(); i++) {
|
||||
PosEncoder.DecodedPositionIncrement inc = decoded.get(i);
|
||||
long time = lastEntry.getTime() + (i + 1) * incrementBy;
|
||||
if (time < startTime) continue;
|
||||
org.bukkit.util.Vector add = new org.bukkit.util.Vector(inc.x(), inc.y(), inc.z());
|
||||
Location incLoc = lastLoc.clone().add(add);
|
||||
if (inc.hasPitch()) incLoc.setPitch(inc.pitch());
|
||||
if (inc.hasYaw()) incLoc.setYaw(inc.yaw());
|
||||
lastLoc = incLoc.clone();
|
||||
PosPoint point = new PosPoint(time, UUID.fromString(entry.getUserUUID().substring(1)), entry.getUser(), entry.getUid(), incLoc.clone(), true);
|
||||
points.add(point);
|
||||
}
|
||||
}
|
||||
Location entryLoc = DbEntryBukkit.getLocation(entry);
|
||||
entryLoc.setYaw(entry.getYaw());
|
||||
entryLoc.setPitch(entry.getPitch());
|
||||
PosPoint point = new PosPoint(entry.getTime(), UUID.fromString(entry.getUserUUID().substring(1)), entry.getUser(), entry.getUid(), entryLoc, false);
|
||||
points.add(point);
|
||||
lastEntries.put(entry.getUser(), entry);
|
||||
}
|
||||
points.sort(Comparator.comparingLong(a -> a.time));
|
||||
return points;
|
||||
}
|
||||
|
||||
public static void close(UUID uuid) {
|
||||
public static void shutdown() {
|
||||
synchronized (instances) {
|
||||
PlaybackSolver instance = instances.get(uuid);
|
||||
if (instance != null) instance.close();
|
||||
instances.values().forEach(PlaybackSolver::close);
|
||||
instances.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,57 +103,112 @@ public class PlaybackSolver extends BukkitRunnable {
|
||||
}
|
||||
}
|
||||
|
||||
public static void close(UUID uuid) {
|
||||
synchronized (instances) {
|
||||
PlaybackSolver instance = instances.get(uuid);
|
||||
if (instance != null) instance.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<PosPoint> getLocations(IAuxProtect plugin, List<DbEntry> entries, long startTime) throws SQLException {
|
||||
if (plugin.getPlatform() != PlatformType.SPIGOT) throw new UnsupportedOperationException();
|
||||
Map<String, DbEntry> lastEntries = new HashMap<>();
|
||||
entries.sort(Comparator.comparingLong(DbEntry::getTime));
|
||||
List<PosPoint> points = new ArrayList<>();
|
||||
for (DbEntry entry : entries) {
|
||||
DbEntry lastEntry = lastEntries.get(entry.getUser());
|
||||
if (lastEntry != null && entry.getBlob() != null) {
|
||||
List<PosEncoder.PositionIncrement> decoded;
|
||||
if (entry.getTime() < plugin.getSqlManager().getLast(SQLManager.LastKeys.LEGACY_POSITIONS)) {
|
||||
decoded = PosEncoder.decodeLegacy(entry.getBlob());
|
||||
} else {
|
||||
decoded = PosEncoder.decode(entry.getBlob());
|
||||
}
|
||||
Location lastLoc = DbEntryBukkit.getLocation(lastEntry);
|
||||
final long incrementBy = (entry.getTime() - lastEntry.getTime()) / (decoded.size() + 1);
|
||||
for (int i = 0; i < decoded.size(); i++) {
|
||||
PosEncoder.PositionIncrement inc = decoded.get(i);
|
||||
long time = lastEntry.getTime() + (i + 1) * incrementBy;
|
||||
if (time < startTime) continue;
|
||||
org.bukkit.util.Vector add = new org.bukkit.util.Vector(inc.x(), inc.y(), inc.z());
|
||||
Location incLoc = lastLoc.clone().add(add);
|
||||
if (inc.hasLook()) {
|
||||
incLoc.setPitch(inc.pitch());
|
||||
incLoc.setYaw(inc.yaw());
|
||||
}
|
||||
lastLoc = incLoc.clone();
|
||||
PosPoint point = new PosPoint(time, UUID.fromString(entry.getUserUUID().substring(1)), entry.getUser(), entry.getUid(), incLoc.clone(), true, inc.posture());
|
||||
points.add(point);
|
||||
}
|
||||
}
|
||||
Location entryLoc = DbEntryBukkit.getLocation(entry);
|
||||
entryLoc.setYaw(entry.getYaw());
|
||||
entryLoc.setPitch(entry.getPitch());
|
||||
PosPoint point = new PosPoint(entry.getTime(), UUID.fromString(entry.getUserUUID().substring(1)), entry.getUser(), entry.getUid(), entryLoc, false, null);
|
||||
points.add(point);
|
||||
lastEntries.put(entry.getUser(), entry);
|
||||
}
|
||||
points.sort(Comparator.comparingLong(a -> a.time));
|
||||
return points;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (closed || isCancelled() || !audience.isOnline()) {
|
||||
close();
|
||||
synchronized (this) {
|
||||
if (closed || isCancelled() || !audience.isOnline()) {
|
||||
close();
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
final long timeNow = System.currentTimeMillis() - realReferenceTime + startTime;
|
||||
|
||||
audience.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(TimeUtil.format(timeNow, TimeUtil.entryTimeFormat) + " §7- " + TimeUtil.millisToString(System.currentTimeMillis() - timeNow) + " ago"));
|
||||
|
||||
|
||||
for (Iterator<PosPoint> it = points.iterator(); it.hasNext(); ) {
|
||||
PosPoint point = it.next();
|
||||
if (timeNow > point.time()) {
|
||||
FakePlayer actor = actors.get(point.name());
|
||||
Location loc = point.location.clone();
|
||||
assert loc.getWorld() != null;
|
||||
|
||||
if (actor == null) {
|
||||
String name = "~" + point.name;
|
||||
if (name.length() > 16) name = name.substring(0, 16);
|
||||
actor = new FakePlayer(name, protocol, audience);
|
||||
actor.spawn(point.location(), skins.get(point.uuid));
|
||||
}
|
||||
actors.put(point.name(), actor);
|
||||
actor.setLocation(loc, false);
|
||||
if (point.posture != null) actor.setPosture(point.posture);
|
||||
|
||||
it.remove();
|
||||
} else break;
|
||||
}
|
||||
Iterator<FakePlayer> it = actors.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
FakePlayer pl = it.next();
|
||||
if (System.currentTimeMillis() - pl.getLastMoved() > 1000) {
|
||||
pl.remove();
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (points.isEmpty()) close();
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (this) {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
if (audience.isOnline()) {
|
||||
audience.sendMessage(Language.translate(Language.L.COMMAND__LOOKUP__PLAYBACK__STOPPED));
|
||||
audience.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(Language.translate(Language.L.COMMAND__LOOKUP__PLAYBACK__STOPPED)));
|
||||
actors.values().forEach(FakePlayer::remove);
|
||||
}
|
||||
actors.clear();
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
final long timeNow = System.currentTimeMillis() - realReferenceTime + startTime;
|
||||
|
||||
audience.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(TimeUtil.format(timeNow, TimeUtil.entryTimeFormat) + " §7- " + TimeUtil.millisToString(System.currentTimeMillis() - timeNow) + " ago"));
|
||||
|
||||
|
||||
for (Iterator<PosPoint> it = points.iterator(); it.hasNext(); ) {
|
||||
PosPoint point = it.next();
|
||||
if (timeNow > point.time()) {
|
||||
FakePlayer actor = actors.get(point.name());
|
||||
Location loc = point.location.clone();
|
||||
assert loc.getWorld() != null;
|
||||
|
||||
if (actor == null) {
|
||||
String name = "~" + point.name;
|
||||
if (name.length() > 16) name = name.substring(0, 16);
|
||||
actor = new FakePlayer(name, protocol, audience);
|
||||
actor.spawn(point.location(), skins.get(point.uuid));
|
||||
}
|
||||
actors.put(point.name(), actor);
|
||||
actor.setLocation(loc);
|
||||
|
||||
it.remove();
|
||||
} else break;
|
||||
}
|
||||
Iterator<FakePlayer> it = actors.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
FakePlayer pl = it.next();
|
||||
if (System.currentTimeMillis() - pl.getLastMoved() > 1000) {
|
||||
pl.remove();
|
||||
it.remove();
|
||||
actors.clear();
|
||||
}
|
||||
}
|
||||
if (points.isEmpty()) close();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@@ -194,6 +216,7 @@ public class PlaybackSolver extends BukkitRunnable {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public record PosPoint(long time, UUID uuid, String name, int uid, Location location, boolean inc) {
|
||||
public record PosPoint(long time, UUID uuid, String name, int uid, Location location, boolean inc,
|
||||
@Nullable PosEncoder.Posture posture) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package dev.heliosares.auxprotect.utils;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
@@ -14,39 +16,57 @@ public class PosEncoder {
|
||||
*
|
||||
* @return The incremental byte array representing:<br>Bit mask indicating the presence/length of values below<br>0-2 bytes representing dX<br>0-2 bytes representing dY<br>0-2 bytes representing dZ<br>1 byte representing pitch<br>1 byte representing yaw
|
||||
*/
|
||||
public static byte[] encode(Location from, Location to) {
|
||||
IncrementalByte diffX = simplify(to.getX() - from.getX());
|
||||
IncrementalByte diffY = simplify(to.getY() - from.getY());
|
||||
IncrementalByte diffZ = simplify(to.getZ() - from.getZ());
|
||||
byte pitch = (byte) to.getPitch();
|
||||
boolean doPitch = to.getPitch() != from.getPitch();
|
||||
byte yaw = (byte) ((to.getYaw() / 180.0) * 127);
|
||||
boolean doYaw = to.getYaw() != from.getYaw();
|
||||
public static byte[] encode(Location from, Location to, Posture posture, @Nullable Posture lastPosture) {
|
||||
return encode(
|
||||
to.getX() - from.getX(),
|
||||
to.getY() - from.getY(),
|
||||
to.getZ() - from.getZ(),
|
||||
to.getPitch() != from.getPitch() || to.getYaw() != from.getYaw(), to.getPitch(), to.getYaw(),
|
||||
posture.equals(lastPosture) ? null : posture);
|
||||
}
|
||||
|
||||
private static byte[] encode(double diffX_, double diffY_, double diffZ_, boolean doLook, float pitch_, float yaw_, @Nullable Posture posture) {
|
||||
IncrementalByte diffX = simplify(diffX_);
|
||||
IncrementalByte diffY = simplify(diffY_);
|
||||
IncrementalByte diffZ = simplify(diffZ_);
|
||||
byte pitch = (byte) pitch_;
|
||||
byte yaw = (byte) ((yaw_ / 180.0) * 127);
|
||||
|
||||
// bitMask indicates the presence of various values
|
||||
// 0-1 represent number of bytes (0-2) representing X. Value of 0b11 indicates fine
|
||||
// 2-3 represent number of bytes (0-2) representing Y. Value of 0b11 indicates fine
|
||||
// 4-5 represent number of bytes (0-2) representing Z. Value of 0b11 indicates fine
|
||||
// 6 represents whether there is pitch
|
||||
// 7 represents whether there is yaw
|
||||
// 6 represents whether there is look (pitch/yaw)
|
||||
// 7 represents whether there is posture data (sneak, gliding, etc.)
|
||||
byte bitMask = 0;
|
||||
int len = 1 + diffX.array.length + diffY.array.length + diffZ.array.length;
|
||||
bitMask |= diffX.getBytesNeeded();
|
||||
bitMask |= diffY.getBytesNeeded() << 2;
|
||||
bitMask |= diffZ.getBytesNeeded() << 4;
|
||||
if (doPitch) bitMask |= 1 << 6;
|
||||
if (doYaw) bitMask -= 128;
|
||||
|
||||
int len = 1 + diffX.array.length + diffY.array.length + diffZ.array.length;
|
||||
if (doPitch) len++;
|
||||
if (doYaw) len++;
|
||||
if (doLook) {
|
||||
bitMask = setBit(bitMask, 6, true);
|
||||
len += 2;
|
||||
}
|
||||
if (posture != null) {
|
||||
bitMask = setBit(bitMask, 7, true);
|
||||
len++;
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer bb = ByteBuffer.allocate(len);
|
||||
bb.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bb.put(bitMask);
|
||||
bb.put(diffX.array);
|
||||
bb.put(diffY.array);
|
||||
bb.put(diffZ.array);
|
||||
if (doPitch) bb.put(pitch);
|
||||
if (doYaw) bb.put(yaw);
|
||||
if (doLook) {
|
||||
bb.put(pitch);
|
||||
bb.put(yaw);
|
||||
}
|
||||
if (posture != null) {
|
||||
bb.put(posture.getID());
|
||||
}
|
||||
|
||||
return bb.array();
|
||||
}
|
||||
@@ -57,10 +77,10 @@ public class PosEncoder {
|
||||
* @param bytes The incremental byte array
|
||||
* @return A list of records representing the presence and value of each component of position.
|
||||
*/
|
||||
public static List<DecodedPositionIncrement> decode(byte[] bytes) {
|
||||
List<DecodedPositionIncrement> out = new ArrayList<>();
|
||||
public static List<PositionIncrement> decode(byte[] bytes) {
|
||||
List<PositionIncrement> out = new ArrayList<>();
|
||||
for (int i = 0, safety = 0; i < bytes.length && safety < bytes.length; safety++) {
|
||||
DecodedPositionIncrement decoded = decodeSingle(bytes, i);
|
||||
PositionIncrement decoded = decodeSingle(bytes, i);
|
||||
i += decoded.bytes;
|
||||
out.add(decoded);
|
||||
}
|
||||
@@ -74,35 +94,48 @@ public class PosEncoder {
|
||||
* @param offset Where to start looking in the data
|
||||
* @return A record representing the presence and value of each component of position
|
||||
*/
|
||||
public static DecodedPositionIncrement decodeSingle(byte[] bytes, int offset) {
|
||||
private static PositionIncrement decodeSingle(byte[] bytes, int offset) {
|
||||
double[] out = new double[5];
|
||||
byte bitMask = bytes[offset];
|
||||
|
||||
boolean yaw = bitMask < 0;
|
||||
if (yaw) bitMask += 128;
|
||||
|
||||
int index = 1;
|
||||
int xLen = bitMask & 0b11;
|
||||
int yLen = (bitMask >> 2) & 0b11;
|
||||
int zLen = (bitMask >> 4) & 0b11;
|
||||
|
||||
if (xLen > 0) out[0] = toDouble(bytes, offset + 1, xLen);
|
||||
if (xLen > 0) out[0] = toDouble(bytes, offset + index, xLen);
|
||||
if (xLen == 3) xLen = 1;
|
||||
if (yLen > 0) out[1] = toDouble(bytes, offset + 1 + xLen, yLen);
|
||||
index += xLen;
|
||||
|
||||
int yLen = (bitMask >> 2) & 0b11;
|
||||
if (yLen > 0) out[1] = toDouble(bytes, offset + index, yLen);
|
||||
if (yLen == 3) yLen = 1;
|
||||
if (zLen > 0) out[2] = toDouble(bytes, offset + 1 + xLen + yLen, zLen);
|
||||
index += yLen;
|
||||
|
||||
int zLen = (bitMask >> 4) & 0b11;
|
||||
if (zLen > 0) out[2] = toDouble(bytes, offset + index, zLen);
|
||||
if (zLen == 3) zLen = 1;
|
||||
index += zLen;
|
||||
|
||||
boolean pitch = (bitMask >> 6 & 1) == 1;
|
||||
if (pitch) out[3] = bytes[offset + 1 + xLen + yLen + zLen];
|
||||
if (yaw) out[4] = (double) bytes[offset + 1 + xLen + yLen + zLen + (pitch ? 1 : 0)] / 127.0 * 180;
|
||||
|
||||
return new DecodedPositionIncrement(
|
||||
boolean look = getBit(bitMask, 6);
|
||||
boolean hasPosture = getBit(bitMask, 7);
|
||||
|
||||
if (look) {
|
||||
out[3] = bytes[offset + index++];
|
||||
out[4] = (double) bytes[offset + index++] / 127.0 * 180;
|
||||
}
|
||||
|
||||
Posture posture = null;
|
||||
if (hasPosture) {
|
||||
posture = Posture.fromID(bytes[offset + index++]);
|
||||
}
|
||||
|
||||
|
||||
return new PositionIncrement(
|
||||
xLen > 0, out[0],
|
||||
yLen > 0, out[1],
|
||||
zLen > 0, out[2],
|
||||
pitch, (float) out[3],
|
||||
yaw, (float) out[4],
|
||||
1 + xLen + yLen + zLen + (yaw ? 1 : 0) + (pitch ? 1 : 0)
|
||||
look, (float) out[3], (float) out[4],
|
||||
hasPosture, posture,
|
||||
index
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,6 +148,7 @@ public class PosEncoder {
|
||||
* @return The double retrieved from the byte array
|
||||
*/
|
||||
private static double toDouble(byte[] bytes, int index, int bitMask) {
|
||||
if (bytes.length == 0) return 0;
|
||||
double sig;
|
||||
if (bitMask == 3) {
|
||||
bitMask = 1;
|
||||
@@ -153,38 +187,6 @@ public class PosEncoder {
|
||||
return new IncrementalByte(new byte[]{(byte) (s >> 8), lower}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the fraction of the x/y/z values into a single byte. The structure is as follows
|
||||
* 0b X X X Y Y Z Z Z
|
||||
* X and Z are stored in 8ths, Y is stored in 4ths.
|
||||
*/
|
||||
public static byte getFractionalByte(double dx, double dy, double dz) {
|
||||
dx %= 1;
|
||||
dy %= 1;
|
||||
dz %= 1;
|
||||
if (dx < 0) dx++;
|
||||
if (dy < 0) dy++;
|
||||
if (dz < 0) dz++;
|
||||
int x = (int) Math.min(Math.round(dx * 8), 7) << 5;
|
||||
int y = (int) Math.min(Math.round(dy * 4), 3) << 3;
|
||||
int z = (int) Math.min(Math.round(dz * 8), 7);
|
||||
|
||||
return (byte) (x | y | z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fractional values from the increment byte generated in {@link PosEncoder#getFractionalByte(double, double, double)}
|
||||
*
|
||||
* @return An array of doubles of length 3, containing the x, y, and z fractions respectively.
|
||||
*/
|
||||
public static double[] byteToFractions(byte b) {
|
||||
int x = (b >> 5) & 0b111;
|
||||
int y = (b >> 3) & 0b11;
|
||||
int z = b & 0b111;
|
||||
|
||||
return new double[]{x / 8D, y / 4D, z / 8D};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array The data
|
||||
* @param fine Whether the value is stored in hundredths or tenths. true indicates hundredths.
|
||||
@@ -195,11 +197,88 @@ public class PosEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
public record DecodedPositionIncrement(boolean hasX, double x, boolean hasY, double y, boolean hasZ, double z,
|
||||
boolean hasPitch, float pitch, boolean hasYaw, float yaw, int bytes) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X=" + x + " Y=" + y + " Z=" + z + " Pitch=" + pitch + " Yaw=" + yaw;
|
||||
public enum Posture {
|
||||
STANDING(0), SNEAKING(1), SWIMMING(2), GLIDING(3), SITTING(4), CRAWLING(5), SLEEPING(6);
|
||||
private final byte id;
|
||||
|
||||
Posture(int id) {
|
||||
this.id = (byte) id;
|
||||
}
|
||||
|
||||
public byte getID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Posture fromPlayer(Player player) {
|
||||
if (player.isSwimming()) return SWIMMING;
|
||||
if (player.isGliding()) return GLIDING;
|
||||
if (player.isInsideVehicle()) return SITTING;
|
||||
if (player.isSleeping()) return SLEEPING;
|
||||
if (player.isSneaking()) return SNEAKING;
|
||||
if (player.getBoundingBox().getHeight() < 1) return CRAWLING;
|
||||
return STANDING;
|
||||
}
|
||||
|
||||
public static Posture fromID(byte id) {
|
||||
for (Posture posture : values()) if (posture.id == id) return posture;
|
||||
throw new IllegalArgumentException("Unknown posture: " + id);
|
||||
}
|
||||
}
|
||||
|
||||
public record PositionIncrement(boolean hasX, double x, boolean hasY, double y, boolean hasZ, double z,
|
||||
boolean hasLook, float pitch, float yaw, boolean hasPosture, Posture posture,
|
||||
int bytes) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "X=" + (hasX ? x : "none") + " Y=" + (hasY ? y : "none") + " Z=" + (hasZ ? z : "none") + " Pitch=" + pitch + " Yaw=" + yaw + " Posture=" + posture;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte setBit(byte b, int index, boolean value) {
|
||||
if (index > 7 || index < 0) throw new IndexOutOfBoundsException(index + " is not a valid byte index.");
|
||||
byte val = (byte) (1 << index);
|
||||
if (value) b |= val;
|
||||
else b &= ~val;
|
||||
return b;
|
||||
}
|
||||
|
||||
public static boolean getBit(byte b, int index) {
|
||||
return ((b >> index) & 1) == 1;
|
||||
}
|
||||
|
||||
public static List<PosEncoder.PositionIncrement> decodeLegacy(byte[] bytes) {
|
||||
List<PosEncoder.PositionIncrement> out = new ArrayList<>();
|
||||
for (int offset = 0, safety = 0; offset < bytes.length && safety < bytes.length; safety++) {
|
||||
double[] doubles = new double[5];
|
||||
byte hdr = bytes[offset];
|
||||
|
||||
boolean yaw = hdr < 0;
|
||||
if (yaw) hdr += 128;
|
||||
|
||||
int xlen = hdr & 0b11;
|
||||
int ylen = (hdr >> 2) & 0b11;
|
||||
int zlen = (hdr >> 4) & 0b11;
|
||||
|
||||
if (xlen > 0) doubles[0] = toDouble(bytes, offset + 1, xlen);
|
||||
if (xlen == 3) xlen = 1;
|
||||
if (ylen > 0) doubles[1] = toDouble(bytes, offset + 1 + xlen, ylen);
|
||||
if (ylen == 3) ylen = 1;
|
||||
if (zlen > 0) doubles[2] = toDouble(bytes, offset + 1 + xlen + ylen, zlen);
|
||||
if (zlen == 3) zlen = 1;
|
||||
|
||||
boolean pitch = (hdr >> 6 & 1) == 1;
|
||||
if (pitch) doubles[3] = bytes[offset + 1 + xlen + ylen + zlen];
|
||||
if (yaw) doubles[4] = (double) bytes[offset + 1 + xlen + ylen + zlen + (pitch ? 1 : 0)] / 127.0 * 180;
|
||||
|
||||
PosEncoder.PositionIncrement decod = new PosEncoder.PositionIncrement(
|
||||
xlen > 0, doubles[0],
|
||||
ylen > 0, doubles[1],
|
||||
zlen > 0, doubles[2], pitch || yaw, (float) doubles[3], (float) doubles[4],
|
||||
false, null,
|
||||
1 + xlen + ylen + zlen + (yaw ? 1 : 0) + (pitch ? 1 : 0));
|
||||
offset += decod.bytes();
|
||||
out.add(decod);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user