Full Folia async support

This commit is contained in:
Axionize committed 2024-11-30 16:06:10 -05:00
1 parent 6d1ab1acbe
commit 73e862a782
9 files changed
+151 -86

No files matched your search

@@ -8,10 +8,10 @@ import io.github.retrooper.packetevents.util.folia.FoliaScheduler;
import io.github.retrooper.packetevents.util.folia.RegionScheduler;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.BukkitBase;
import me.caseload.knockbacksync.listener.bukkit.BukkitPlayerKnockbackListener;
import me.caseload.knockbacksync.async.AsyncOperation;
import me.caseload.knockbacksync.async.FoliaOperation;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
import org.bukkit.Bukkit;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.World;
@@ -32,7 +32,7 @@ public class FoliaWorld extends SpigotWorld {
}
@Override
public WrappedBlockState getBlockStateAt(int x, int y, int z) {
public AsyncOperation<WrappedBlockState> getBlockStateAt(int x, int y, int z) {
Location location = new Location(super.world, x, y, z);
CompletableFuture<WrappedBlockState> future = new CompletableFuture<>();
@@ -41,49 +41,37 @@ public class FoliaWorld extends SpigotWorld {
future.complete(SpigotConversionUtil.fromBukkitBlockData(block.getBlockData()));
});
try {
return future.get(); // Wait for the result
} catch (Exception e) {
e.printStackTrace();
return null; // Handle error appropriately
}
return new FoliaOperation<>(future);
}
@Override
public RayTraceResult rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
public AsyncOperation<RayTraceResult> rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
Location location = new Location(super.world, start.getX(), start.getY(), start.getZ());
CompletableFuture<RayTraceResult> future = new CompletableFuture<>();
// Yes this code is bad; I know it's bad. We did not design this plugin around Folia, too bad!
scheduler.execute(plugin, location, () -> {
// Existing 1.13+ code using world.rayTraceBlocks
Vector startVec = new Vector(start.getX(), start.getY(), start.getZ());
Vector directionVec = new Vector(direction.getX(), direction.getY(), direction.getZ());
Vector startVec = new Vector(start.getX(), start.getY(), start.getZ());
Vector directionVec = new Vector(direction.getX(), direction.getY(), direction.getZ());
FluidCollisionMode fluidMode = (fluidHandling == FluidHandling.NONE) ? FluidCollisionMode.NEVER :
(fluidHandling == FluidHandling.SOURCE_ONLY) ? FluidCollisionMode.SOURCE_ONLY :
FluidCollisionMode.ALWAYS;
FluidCollisionMode fluidMode = (fluidHandling == FluidHandling.NONE) ? FluidCollisionMode.NEVER :
(fluidHandling == FluidHandling.SOURCE_ONLY) ? FluidCollisionMode.SOURCE_ONLY :
FluidCollisionMode.ALWAYS;
org.bukkit.util.RayTraceResult result = super.world.rayTraceBlocks(startVec.toLocation(super.world), directionVec, maxDistance, fluidMode, ignorePassableBlocks);
org.bukkit.util.RayTraceResult result = super.world.rayTraceBlocks(startVec.toLocation(super.world), directionVec, maxDistance, fluidMode, ignorePassableBlocks);
if (result == null) {
future.complete(null);
return;
}
if (result == null) {
future.complete(null);
return;
}
future.complete(new RayTraceResult(
new Vector3d(result.getHitPosition().getX(), result.getHitPosition().getY(), result.getHitPosition().getZ()),
getBlockFaceFrom(result.getHitBlockFace()),
new Vector3i(result.getHitBlock().getX(), result.getHitBlock().getY(), result.getHitBlock().getY()),
result.getHitBlock() != null ? SpigotConversionUtil.fromBukkitBlockData(result.getHitBlock().getBlockData()) : null
));
future.complete(new RayTraceResult(
new Vector3d(result.getHitPosition().getX(), result.getHitPosition().getY(), result.getHitPosition().getZ()),
getBlockFaceFrom(result.getHitBlockFace()),
new Vector3i(result.getHitBlock().getX(), result.getHitBlock().getY(), result.getHitBlock().getY()),
result.getHitBlock() != null ? SpigotConversionUtil.fromBukkitBlockData(result.getHitBlock().getBlockData()) : null
));
});
try {
return future.get(); // Wait for the result
} catch (Exception e) {
e.printStackTrace();
return null; // Handle error appropriately
}
return new FoliaOperation<>(future);
}
}
@@ -7,6 +7,8 @@ import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import io.github.retrooper.packetevents.util.SpigotConversionUtil;
import me.caseload.knockbacksync.async.AsyncOperation;
import me.caseload.knockbacksync.async.SyncOperation;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
import org.bukkit.Bukkit;
@@ -78,18 +80,18 @@ public class SpigotWorld implements PlatformWorld {
}
@Override
public WrappedBlockState getBlockStateAt(int x, int y, int z) {
public AsyncOperation<WrappedBlockState> getBlockStateAt(int x, int y, int z) {
Block block = world.getBlockAt(x, y, z);
return SpigotConversionUtil.fromBukkitBlockData(block.getBlockData());
return new SyncOperation<>(SpigotConversionUtil.fromBukkitBlockData(block.getBlockData()));
}
@Override
public WrappedBlockState getBlockStateAt(Vector3d loc) {
public AsyncOperation<WrappedBlockState> getBlockStateAt(Vector3d loc) {
return getBlockStateAt((int) Math.floor(loc.x), (int) Math.floor(loc.y), (int) Math.floor(loc.z));
}
@Override
public RayTraceResult rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
public AsyncOperation<RayTraceResult> rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThan(ServerVersion.V_1_12_2)) {
Vector startVec = new Vector(start.getX(), start.getY(), start.getZ());
Vector directionVec = new Vector(direction.getX(), direction.getY(), direction.getZ());
@@ -102,12 +104,12 @@ public class SpigotWorld implements PlatformWorld {
if (result == null) return null;
return new RayTraceResult(
return new SyncOperation<>(new RayTraceResult(
new Vector3d(result.getHitPosition().getX(), result.getHitPosition().getY(), result.getHitPosition().getZ()),
getBlockFaceFrom(result.getHitBlockFace()),
new Vector3i(result.getHitBlock().getX(), result.getHitBlock().getY(), result.getHitBlock().getZ()),
result.getHitBlock() != null ? SpigotConversionUtil.fromBukkitBlockData(result.getHitBlock().getBlockData()) : null
);
));
// Only tested on 1.12.2
} else {
try {
@@ -130,14 +132,14 @@ public class SpigotWorld implements PlatformWorld {
Object hitDirection = hitResult.getClass().getField("direction").get(hitResult);
Object hitBlock = hitResult.getClass().getField("e").get(hitResult); // e = BlockHitResult
return new RayTraceResult(
return new SyncOperation<>(new RayTraceResult(
hitPosition,
getHitBlockFace(hitDirection),
new Vector3i((int) hitBlock.getClass().getField("x").get(hitBlock),
(int) hitBlock.getClass().getField("y").get(hitBlock),
(int) hitBlock.getClass().getField("z").get(hitBlock)),
hitBlock != null ? WrappedBlockState.getByString(hitBlock.getClass().getField("type").get(hitBlock).toString()) : null
);
));
} catch (Exception e) {
e.printStackTrace();
return null; // Return null if there was an error during ray tracing
@@ -0,0 +1,9 @@
package me.caseload.knockbacksync.async;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
// Platform-agnostic interface
public interface AsyncOperation<T> {
CompletableFuture<T> asFuture();
}
@@ -0,0 +1,18 @@
package me.caseload.knockbacksync.async;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
// Async implementation for Folia
public class FoliaOperation<T> implements AsyncOperation<T> {
private final CompletableFuture<T> future;
public FoliaOperation(CompletableFuture<T> future) {
this.future = future;
}
@Override
public CompletableFuture<T> asFuture() {
return future;
}
}
@@ -0,0 +1,17 @@
package me.caseload.knockbacksync.async;
import java.util.concurrent.CompletableFuture;
// Paper/Fabric implementation
public class SyncOperation<T> implements AsyncOperation<T> {
private final T result;
public SyncOperation(T result) {
this.result = result;
}
@Override
public CompletableFuture<T> asFuture() {
return CompletableFuture.completedFuture(result);
}
}
@@ -8,6 +8,8 @@ import me.caseload.knockbacksync.manager.PlayerDataManager;
import me.caseload.knockbacksync.player.PlatformPlayer;
import me.caseload.knockbacksync.player.PlayerData;
import java.util.concurrent.CompletableFuture;
public abstract class PlayerKnockbackListener {
public void onPlayerVelocity(PlatformPlayer victim, Vector3d velocity) {
@@ -21,31 +23,35 @@ public abstract class PlayerKnockbackListener {
if (victimPlayerData.getNotNullPing() < PlayerData.PING_OFFSET)
return;
double distanceToGround = victimPlayerData.getDistanceToGround();
if (distanceToGround <= 0)
return; // minecraft already does the work for us
victimPlayerData.getDistanceToGround().asFuture()
.thenCompose(distanceToGround -> {
if (distanceToGround <= 0)
return CompletableFuture.completedFuture(null);
WrappedBlockState blockState = victim.getWorld().getBlockStateAt(victim.getLocation());
if (victim.isGliding() ||
blockState.getType() == StateTypes.WATER ||
blockState.getType() == StateTypes.LAVA ||
blockState.getType() == StateTypes.COBWEB ||
blockState.getType() == StateTypes.SCAFFOLDING)
return;
return victim.getWorld().getBlockStateAt(victim.getLocation()).asFuture()
.thenAccept(blockState -> {
if (victim.isGliding() ||
blockState.getType() == StateTypes.WATER ||
blockState.getType() == StateTypes.LAVA ||
blockState.getType() == StateTypes.COBWEB ||
blockState.getType() == StateTypes.SCAFFOLDING)
return;
Vector3d adjustedVelocity;
if (victimPlayerData.isOnGroundClientSide(velocity.getY(), distanceToGround)) {
Integer damageTicks = victimPlayerData.getLastDamageTicks();
if (damageTicks != null && damageTicks > 8)
return;
Vector3d adjustedVelocity;
if (victimPlayerData.isOnGroundClientSide(velocity.getY(), distanceToGround)) {
Integer damageTicks = victimPlayerData.getLastDamageTicks();
if (damageTicks != null && damageTicks > 8)
return;
adjustedVelocity = velocity.withY(victimPlayerData.getVerticalVelocity()); // Should be impossible to produce a NPE in this context
}
else if (victimPlayerData.isOffGroundSyncEnabled())
adjustedVelocity = velocity.withY(victimPlayerData.getCompensatedOffGroundVelocity());
else
return;
adjustedVelocity = velocity.withY(victimPlayerData.getVerticalVelocity());
}
else if (victimPlayerData.isOffGroundSyncEnabled())
adjustedVelocity = velocity.withY(victimPlayerData.getCompensatedOffGroundVelocity());
else
return;
victim.setVelocity(adjustedVelocity);
victim.setVelocity(adjustedVelocity);
});
});
}
}
@@ -16,6 +16,9 @@ import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerWi
import lombok.Getter;
import lombok.Setter;
import me.caseload.knockbacksync.Base;
import me.caseload.knockbacksync.async.AsyncOperation;
import me.caseload.knockbacksync.async.FoliaOperation;
import me.caseload.knockbacksync.async.SyncOperation;
import me.caseload.knockbacksync.command.subcommand.ToggleOffGroundSubcommand;
import me.caseload.knockbacksync.event.KBSyncEventHandler;
import me.caseload.knockbacksync.event.events.ToggleOnOffEvent;
@@ -31,6 +34,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
@Getter
@@ -231,23 +235,35 @@ public class PlayerData {
*
* @return The distance to the ground in blocks
*/
public double getDistanceToGround() {
double collisionDist = 5;
public AsyncOperation<Double> getDistanceToGround() {
PlatformWorld world = platformPlayer.getWorld();
Vector3d[] corners = getBBCorners();
for (Vector3d corner : getBBCorners()) {
RayTraceResult result = world.rayTraceBlocks(corner, new Vector3d(0, -1, 0), 5, FluidHandling.NONE, true);
// Start with the first corner
CompletableFuture<Double> future = processCorner(world, corners, 0, 5.0);
if (result == null || result.getHitBlock() == null)
continue;
return new FoliaOperation<>(future);
}
collisionDist = Math.min(collisionDist, corner.getY() - result.getHitBlockPosition().getY());
private CompletableFuture<Double> processCorner(PlatformWorld world, Vector3d[] corners, int index, double currentMin) {
if (index >= corners.length) {
return CompletableFuture.completedFuture(currentMin - 1);
}
return collisionDist - 1;
return world.rayTraceBlocks(corners[index], new Vector3d(0, -1, 0), 5, FluidHandling.NONE, true)
.asFuture()
.thenCompose(result -> {
double newMin = currentMin;
if (result != null && result.getHitBlock() != null) {
newMin = Math.min(currentMin, corners[index].getY() - result.getHitBlockPosition().getY());
}
// Process next corner
return processCorner(world, corners, index + 1, newMin);
});
}
/**
* Gets the corners of the Player's bounding box.
*
@@ -2,13 +2,12 @@ package me.caseload.knockbacksync.world;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import me.caseload.knockbacksync.async.AsyncOperation;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
public interface PlatformWorld {
WrappedBlockState getBlockStateAt(int x, int y, int z);
WrappedBlockState getBlockStateAt(Vector3d loc);
RayTraceResult rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks);
AsyncOperation<WrappedBlockState> getBlockStateAt(int x, int y, int z);
AsyncOperation<WrappedBlockState> getBlockStateAt(Vector3d loc);
AsyncOperation<RayTraceResult> rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks);
}
@@ -4,6 +4,8 @@ import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import me.caseload.knockbacksync.async.AsyncOperation;
import me.caseload.knockbacksync.async.SyncOperation;
import me.caseload.knockbacksync.world.raytrace.FluidHandling;
import me.caseload.knockbacksync.world.raytrace.RayTraceResult;
import net.minecraft.core.BlockPos;
@@ -17,6 +19,8 @@ import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import java.util.concurrent.CompletableFuture;
public class FabricWorld implements PlatformWorld {
private final Level world;
@@ -25,18 +29,18 @@ public class FabricWorld implements PlatformWorld {
}
@Override
public WrappedBlockState getBlockStateAt(int x, int y, int z) {
public AsyncOperation<WrappedBlockState> getBlockStateAt(int x, int y, int z) {
BlockState blockState = this.world.getBlockState(new BlockPos(x, y, z));
return WrappedBlockState.getByGlobalId(Block.BLOCK_STATE_REGISTRY.getId(blockState));
return new SyncOperation<>(WrappedBlockState.getByGlobalId(Block.BLOCK_STATE_REGISTRY.getId(blockState)));
}
@Override
public WrappedBlockState getBlockStateAt(Vector3d loc) {
public AsyncOperation<WrappedBlockState> getBlockStateAt(Vector3d loc) {
return getBlockStateAt((int) Math.floor(loc.x), (int) Math.floor(loc.x), (int) Math.floor(loc.x));
}
@Override
public RayTraceResult rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
public AsyncOperation<RayTraceResult> rayTraceBlocks(Vector3d start, Vector3d direction, double maxDistance, FluidHandling fluidHandling, boolean ignorePassableBlocks) {
Vec3 startVec = new Vec3(start.getX(), start.getY(), start.getZ());
Vec3 endVec = startVec.add(direction.getX() * maxDistance, direction.getY() * maxDistance, direction.getZ() * maxDistance);
@@ -50,15 +54,21 @@ public class FabricWorld implements PlatformWorld {
CollisionContext.empty()
));
if (result.getType() == HitResult.Type.MISS) return null;
if (result.getType() == HitResult.Type.MISS) {
return new SyncOperation<>(null);
}
Vec3 hitLocation = result.getLocation();
BlockPos blockPos = result.getBlockPos();
return new RayTraceResult(
// Since we're in Fabric, we can just get the block state synchronously
WrappedBlockState blockState = getBlockStateAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()).asFuture().join();
return new SyncOperation<>(new RayTraceResult(
new Vector3d(hitLocation.x, hitLocation.y, hitLocation.z),
BlockFace.getBlockFaceByValue(result.getDirection().ordinal()),
new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ()),
getBlockStateAt(blockPos.getX(), blockPos.getY(), blockPos.getZ())
);
blockState
));
}
}