Compare commits

..
Author SHA1 Message Date
Axionize bf80d63c4a Address code review concerns 2025-10-31 14:14:55 -04:00
Axionize 540fc454b9 Minor code review changes 2025-10-31 07:54:56 -04:00
Axionize cad28d6342 Merge remote-tracking branch 'origin/2.0' into optimize/vector-objects
# Conflicts:
#	common/src/main/java/ac/grim/grimac/utils/data/ReachInterpolationData.java
2025-10-30 13:29:09 -04:00
Axionize 978cbb293c Address more code review concerns 2025-10-30 13:27:43 -04:00
Axionize 42ce2122a8 Apply code review suggestions for Reachutils, BlockModifications, and CheckManagerListener 2025-10-30 13:18:59 -04:00
Axionize 096b01213c Fix code review concerns for break checks 2025-10-30 13:15:07 -04:00
SamB440 f8a5315fff We do not need high bound if player ticks reliably. 2025-10-28 13:27:54 +00:00
Axionize 22e9a649c5 Make input Vec2d 2025-10-27 17:02:48 -04:00
Axionize 8e470d82bc Primitivify inputs 2025-10-27 17:02:48 -04:00
Axionize de083b7b84 Make VectorData vector use primitives 2025-10-27 17:02:48 -04:00
Axionize 3f86a097de More TeleportData conversions 2025-10-27 17:02:48 -04:00
Axionize 1b3b3d7b1d Optimize Collisions forEach predicates 2025-10-27 17:02:48 -04:00
Axionize 40aa1eadbe Primitize more vehicle teleport code 2025-10-27 17:02:48 -04:00
Axionize fac7d98530 use more primitives for teleports 2025-10-27 17:02:48 -04:00
Axionize b9f16762f1 Make BlockBreak object use primitives 2025-10-27 17:02:48 -04:00
Axionize 441af405b7 Optimize usage of vector3i.getSerializedPosition() 2025-10-27 17:02:48 -04:00
Axionize 71fb934a7e Improve performance of updateBlock() 2025-10-27 17:02:48 -04:00
Axionize fe1d3b2b93 Fix minor bug and cleanup primitiiveify ReachInterpolationData 2025-10-27 17:02:48 -04:00
Axionize a408946c0e De Vector-ify TrackedPosition 2025-10-27 17:02:48 -04:00
Axionize f1192069ae Optimize MainSupportingBlockPosFinder 2025-10-27 17:02:48 -04:00
Axionize 09139062c6 Optimize VehiclePositionUpdate, PositionUpdate, and some VectorUtils usage 2025-10-27 17:02:48 -04:00
Axionize 813551a093 Optimize TeleportData 2025-10-27 17:02:47 -04:00
Axionize 3028b4d274 Improve Vector allocations in Reach, BlockPos, ReachUtils 2025-10-27 17:02:47 -04:00
Axionize 7daaca95eb More vector optimizations 2025-10-27 17:02:47 -04:00
AnnotationVisitor 102b5842e7 Add copper golem statue, shelf collision boxes and other missing 1.21.10 stuff (#2315)
* add copper golem statue

* Include ViaVersion substitution

* add shelves

* also add copper armor

* add mannequin box sizes

* Add copper golem size
2025-10-27 11:17:09 -06:00
ManInMyVan a76bf9ce09 add copper golem statue collision box 2025-10-26 19:01:29 -06:00
ManInMyVan 4f3e715f94 add shelf collision box 2025-10-26 18:41:25 -06:00
ManInMyVan 1b47e31776 allow up to one PLAYER_INPUT per tick (#2306) 2025-10-26 11:54:15 -06:00
BT (calcastor/mame) b69906f809 Clear active item before updating inventory on legacy (#2317)
* Apply spotless

* Clear active item before updating inventory on legacy
2025-10-23 22:56:12 -06:00
ManInMyVan 9ba4052c35 possibly fix build 2025-10-23 00:51:27 -06:00
ManInMyVan a86bf57a39 mark MultiActionsD as stable 2025-10-23 00:49:50 -06:00
ManInMyVan a82f2b888a mark MultiActionsC as stable 2025-10-23 00:49:23 -06:00
ManInMyVan 89cf181772 mark PositionBreakB as stable 2025-10-23 00:43:47 -06:00
ManInMyVan 6e5ec332dd remove double space in ChatB 2025-10-23 00:40:59 -06:00
ManInMyVan 79bf7e4fd1 mark ElytraF as stable 2025-10-23 00:39:32 -06:00
ManInMyVan d0aafdd9f7 mark ElytraA as stable 2025-10-22 18:06:48 -06:00
ManInMyVan 3f19abc859 mark ElytraC as stable 2025-10-22 18:06:29 -06:00
ManInMyVan 8a6863b9d3 mark ElytraB as stable 2025-10-22 17:47:05 -06:00
Axionize 26492fd8e3 Optimize new Vector3dm() multiply calls 2025-10-20 23:23:39 -04:00
Axionize 15e809835b Optimize new object creation on vector add() calls 2025-10-20 23:15:23 -04:00
Axionize 1eae5484c7 Micro optimize DefaultResyncHandler 2025-10-20 22:56:11 -04:00
Axionize 90036e2bf2 Optimize Messages (#2309)
* Optimize MessageUtil.replacePlaceHolders()

* Send user messages at packet level bypassing backend server for performance

* Stash temporary changes

* Make Grim send fast chat messages bypasses platform by default
2025-10-20 22:16:21 -04:00
85 changed files with 1363 additions and 796 deletions

No files matched your search

@@ -6,6 +6,8 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.platform.bukkit.utils.reflection.PaperUtils;
import ac.grim.grimac.platform.bukkit.world.BukkitPlatformWorld;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
@@ -15,6 +17,8 @@ import java.util.concurrent.CompletableFuture;
public class BukkitGrimEntity implements GrimEntity {
protected static final boolean CAN_USE_DIRECT_GETTERS = PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_20_1) && PaperUtils.PAPER;
private final Entity entity;
private BukkitPlatformWorld bukkitPlatformWorld;
@@ -75,4 +79,26 @@ public class BukkitGrimEntity implements GrimEntity {
location.getPitch()
);
}
@Override
public double distanceSquared(double oX, double oY, double oZ) {
if (CAN_USE_DIRECT_GETTERS) {
double x = this.entity.getX();
double y = this.entity.getY();
double z = this.entity.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
} else {
org.bukkit.Location location = this.entity.getLocation();
double x = location.getX();
double y = location.getY();
double z = location.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
}
}
}
@@ -5,9 +5,8 @@ import ac.grim.grimac.platform.bukkit.utils.convert.BukkitConversionUtils;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.PistonData;
import ac.grim.grimac.utils.data.TrackedPosition;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
@@ -28,10 +27,11 @@ public class PistonEvent implements Listener {
private static final double MAX_VERTICAL_DISTANCE = 64.0;
// accuracy isn't that important, it's close enough and performant
private static boolean isCloseEnough(Vector3i vectorA, Vector3d vectorB) {
return Math.abs(vectorA.getX() - vectorB.getX()) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(vectorA.getY() - vectorB.getY()) <= MAX_VERTICAL_DISTANCE
&& Math.abs(vectorA.getZ() - vectorB.getZ()) <= MAX_HORIZONTAL_DISTANCE;
private static boolean isCloseEnough(final int aX, final int aY, final int aZ,
final double bX, final double bY, final double bZ) {
return Math.abs(aX - bX) <= MAX_HORIZONTAL_DISTANCE
&& Math.abs(aY - bY) <= MAX_VERTICAL_DISTANCE
&& Math.abs(aZ - bZ) <= MAX_HORIZONTAL_DISTANCE;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -71,10 +71,13 @@ public class PistonEvent implements Listener {
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
final BlockFace blockFace = BukkitConversionUtils.fromBukkitFace(event.getDirection());
final Vector3i sourcePos = new Vector3i(piston.getX(), piston.getY(), piston.getZ());
final int sourceX = piston.getX();
final int sourceY = piston.getY();
final int sourceZ = piston.getZ();
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
final int lastTrans = player.lastTransactionSent.get();
PistonData data = new PistonData(blockFace, boxes, lastTrans, true, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
@@ -127,12 +130,16 @@ public class PistonEvent implements Listener {
}
}
final int chunkX = event.getBlock().getX() >> 4;
final int chunkZ = event.getBlock().getZ() >> 4;
Vector3i sourcePos = new Vector3i(event.getBlock().getX(), event.getBlock().getY(), event.getBlock().getZ());
final Block block = event.getBlock();
final int chunkX = block.getX() >> 4;
final int chunkZ = block.getZ() >> 4;
final int sourceX = block.getX();
final int sourceY = block.getY();
final int sourceZ = block.getZ();
for (GrimPlayer player : GrimAPI.INSTANCE.getPlayerDataManager().getEntries()) {
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourcePos, player.compensatedEntities.self.trackedServerPosition.getPos())) {
TrackedPosition position = player.compensatedEntities.self.trackedServerPosition;
if (player.compensatedWorld.isChunkLoaded(chunkX, chunkZ) && isCloseEnough(sourceX, sourceY, sourceZ, position.getX(), position.getY(), position.getZ())) {
int lastTrans = player.lastTransactionSent.get();
PistonData data = new PistonData(face, boxes, lastTrans, false, hasSlimeBlock, hasHoneyBlock);
player.latencyUtils.addRealTimeTaskAsync(lastTrans, () -> player.compensatedWorld.activePistons.add(data));
@@ -177,10 +177,11 @@ public class BukkitItemResetHandler implements ItemResetHandler {
if (legacy) { // 1.8.8
resetItemUsage = player -> {
try {
method.invoke(getHandle.invoke(player));
// in 1.8 we need to resync item usage manually,
// only do so if the player is using an item
if (isUsingItem.test(player)) player.updateInventory();
method.invoke(getHandle.invoke(player));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
@@ -112,8 +112,12 @@ public class BukkitPlatformPlayer extends BukkitGrimEntity implements PlatformPl
@Override
public Vector3d getPosition() {
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
if (CAN_USE_DIRECT_GETTERS) {
return new Vector3d(this.bukkitPlayer.getX(), this.bukkitPlayer.getY(), this.bukkitPlayer.getZ());
} else {
org.bukkit.Location location = this.bukkitPlayer.getLocation();
return new Vector3d(location.getX(), location.getY(), location.getZ());
}
}
@Override
@@ -0,0 +1,32 @@
package ac.grim.grimac.checks.impl.badpackets;
import ac.grim.grimac.checks.Check;
import ac.grim.grimac.checks.CheckData;
import ac.grim.grimac.checks.type.PacketCheck;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "BadPacketsZ", experimental = true)
public class BadPacketsZ extends Check implements PacketCheck {
private boolean sent;
public BadPacketsZ(GrimPlayer player) {
super(player);
}
@Override
public void onPacketReceive(PacketReceiveEvent event) {
if (event.getPacketType() == PacketType.Play.Client.CLIENT_TICK_END) {
sent = false;
}
if (event.getPacketType() == PacketType.Play.Client.PLAYER_INPUT) {
if (sent) {
flagAndAlert();
}
sent = true;
}
}
}
@@ -20,8 +20,8 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
private int lastTick;
private boolean didLastFlag;
// Initialize to non-null values to prevent NPE when checking for blockType properties and if position equals old position
private @NotNull Vector3i lastBreakLoc = new Vector3i();
private @NotNull StateType lastBlockType = StateTypes.AIR;
private int lastX, lastY, lastZ;
private float lastHardness, lastBlastResistance;
public AirLiquidBreak(GrimPlayer player) {
super(player);
@@ -40,15 +40,20 @@ public class AirLiquidBreak extends Check implements BlockBreakCheck {
// I am explicitly making this patch as narrow and specific as possible to potentially discover other blocks that exhibit similar behaviour
int newTick = GrimAPI.INSTANCE.getTickManager().currentTick;
if (lastTick == newTick
&& lastBreakLoc.equals(blockBreak.position)
&& lastX == blockBreak.x
&& lastY == blockBreak.y
&& lastZ == blockBreak.z
&& !didLastFlag
&& lastBlockType.getHardness() == 0.0F
&& lastBlockType.getBlastResistance() == 0.0F
&& lastHardness == 0.0F
&& lastBlastResistance == 0.0F
&& block == StateTypes.WATER
) return;
lastTick = newTick;
lastBreakLoc = blockBreak.position;
lastBlockType = block;
lastX = blockBreak.x;
lastY = blockBreak.y;
lastZ = blockBreak.z;
lastHardness = block.getHardness();
lastBlastResistance = block.getBlastResistance();
// the block does not have a hitbox
boolean invalid = (block == StateTypes.LIGHT && !(player.inventory.getHeldItem().is(ItemTypes.LIGHT) || player.inventory.getOffHand().is(ItemTypes.LIGHT)))
@@ -25,10 +25,9 @@ public class FarBreak extends Check implements BlockBreakCheck {
double min = Double.MAX_VALUE;
for (double d : player.getPossibleEyeHeights()) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
}
// getPickRange() determines this?
@@ -36,8 +36,11 @@ public class FastBreak extends Check implements BlockBreakCheck {
super(playerData);
}
// The block the player is currently breaking
Vector3i targetBlockPosition = null;
// The block the player is currently breaking, represented by primitives for performance.
// `targetBlockIsSet` tracks nullness of the position, since primitives can't be null
private boolean targetBlockIsSet;
private int targetBlockX, targetBlockY, targetBlockZ;
// The maximum amount of damage the player deals to the block
//
double maximumBlockDamage = 0;
@@ -67,8 +70,11 @@ public class FastBreak extends Check implements BlockBreakCheck {
// * can we translate back "up" to server version and run check against server version to avoid loading older registries?
WrappedBlockState block = clientOlderThanServer ? WrappedBlockState.getByGlobalId(player.getClientVersion(), player.getViaTranslatedClientBlockID(blockBreak.block.getGlobalId())) : blockBreak.block;
startBreak = System.currentTimeMillis() - (targetBlockPosition == null ? 50 : 0); // ???
targetBlockPosition = blockBreak.position;
startBreak = System.currentTimeMillis() - (!targetBlockIsSet ? 50 : 0); // ???
targetBlockIsSet = true;
targetBlockX = blockBreak.x;
targetBlockY = blockBreak.y;
targetBlockZ = blockBreak.z;
maximumBlockDamage = BlockBreakSpeed.getBlockDamage(player, block);
@@ -87,9 +93,7 @@ public class FastBreak extends Check implements BlockBreakCheck {
}
clampBalance();
}
if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockPosition != null) {
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING && targetBlockIsSet) {
double predictedTime = Math.ceil(1 / maximumBlockDamage) * 50;
double realTime = System.currentTimeMillis() - startBreak;
double diff = predictedTime - realTime;
@@ -117,8 +121,8 @@ public class FastBreak extends Check implements BlockBreakCheck {
public void onPacketReceive(PacketReceiveEvent event) {
// Find the most optimal block damage using the animation packet, which is sent at least once a tick when breaking blocks
// On 1.8 clients, via screws with this packet meaning we must fall back to the 1.8 idle flying packet
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockPosition != null) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockPosition)));
if ((player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? event.getPacketType() == PacketType.Play.Client.ANIMATION : WrapperPlayClientPlayerFlying.isFlying(event.getPacketType())) && targetBlockIsSet) {
maximumBlockDamage = Math.max(maximumBlockDamage, BlockBreakSpeed.getBlockDamage(player, player.compensatedWorld.getBlock(targetBlockX, targetBlockY, targetBlockZ)));
}
}
@@ -21,7 +21,9 @@ public class MultiBreak extends Check implements BlockBreakCheck {
private final List<String> flags = new ArrayList<>();
private boolean hasBroken;
private BlockFace lastFace;
private Vector3i lastPos;
private int lastX;
private int lastY;
private int lastZ;
public MultiBreak(GrimPlayer player) {
super(player);
@@ -33,10 +35,22 @@ public class MultiBreak extends Check implements BlockBreakCheck {
return;
}
if (hasBroken && (blockBreak.face != lastFace || !blockBreak.position.equals(lastPos))) {
final int currentX = blockBreak.x;
final int currentY = blockBreak.y;
final int currentZ = blockBreak.z;
if (hasBroken && (blockBreak.face != lastFace ||
currentX != lastX ||
currentY != lastY ||
currentZ != lastZ)) {
final String currentPosString = MessageUtil.toUnlabeledString(currentX, currentY, currentZ);
final String lastPosString = MessageUtil.toUnlabeledString(lastX, lastY, lastZ);
final String verbose = "face=" + blockBreak.face + ", lastFace=" + lastFace
+ ", pos=" + MessageUtil.toUnlabledString(blockBreak.position)
+ ", lastPos=" + MessageUtil.toUnlabledString(lastPos);
+ ", pos=" + currentPosString
+ ", lastPos=" + lastPosString;
if (!player.canSkipTicks()) {
if (flagAndAlert(verbose) && shouldModifyPackets()) {
blockBreak.cancel();
@@ -47,7 +61,9 @@ public class MultiBreak extends Check implements BlockBreakCheck {
}
lastFace = blockBreak.face;
lastPos = blockBreak.position;
lastX = currentX;
lastY = currentY;
lastZ = currentZ;
hasBroken = true;
}
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
@CheckData(name = "PositionBreakB", experimental = true)
@CheckData(name = "PositionBreakB")
public class PositionBreakB extends Check implements BlockBreakCheck {
private final int releaseFace = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8) ? 0 : 255;
private BlockFace lastFace;
@@ -7,6 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.anticheat.update.BlockBreak;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.Ray;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
@@ -14,6 +15,7 @@ import com.github.retrooper.packetevents.protocol.attribute.Attributes;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.DiggingAction;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.protocol.teleport.RelativeFlag;
import com.github.retrooper.packetevents.protocol.world.BlockFace;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3f;
@@ -70,7 +72,7 @@ public class RotationBreak extends Check implements BlockBreakCheck {
}
private boolean didRayTraceHit(BlockBreak blockBreak) {
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.position);
SimpleCollisionBox box = new SimpleCollisionBox(blockBreak.x, blockBreak.y, blockBreak.z);
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
@@ -111,7 +113,8 @@ public class RotationBreak extends Check implements BlockBreakCheck {
for (Vector3f lookDir : possibleLookDirs) {
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() != null) return true;
}
@@ -19,7 +19,14 @@ import static ac.grim.grimac.utils.nmsutil.BlockBreakSpeed.getBlockDamage;
public class WrongBreak extends Check implements BlockBreakCheck {
private final int exemptedY = player.getClientVersion().isOlderThan(ClientVersion.V_1_8) ? 255 : (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_14) ? -1 : 4095);
private boolean lastBlockWasInstantBreak = false;
private Vector3i lastBlock, lastCancelledBlock, lastLastBlock = null;
private boolean lastBlockIsSet;
private int lastBlockX, lastBlockY, lastBlockZ;
private boolean lastCancelledBlockIsSet;
private int lastCancelledBlockX, lastCancelledBlockY, lastCancelledBlockZ;
private boolean lastLastBlockIsSet;
public WrongBreak(final GrimPlayer player) {
super(player);
@@ -27,8 +34,7 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// The client sometimes sends a wierd cancel packet
private boolean shouldExempt(final WrappedBlockState block, int yPos) {
// lastLastBlock is always null when this happens, and lastBlock isn't
if (lastLastBlock != null || lastBlock == null)
if (lastLastBlockIsSet || !lastBlockIsSet)
return false;
// on pre 1.14.4 clients, the YPos of this packet is always the same
@@ -41,22 +47,29 @@ public class WrongBreak extends Check implements BlockBreakCheck {
@Override
public void onBlockBreak(BlockBreak blockBreak) {
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
if (blockBreak.action == DiggingAction.START_DIGGING) {
final Vector3i pos = blockBreak.position;
lastBlockWasInstantBreak = getBlockDamage(player, blockBreak.block) >= 1;
lastCancelledBlock = null;
lastLastBlock = lastBlock;
lastBlock = pos;
}
lastCancelledBlockIsSet = false;
if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
final Vector3i pos = blockBreak.position;
lastLastBlockIsSet = lastBlockIsSet;
if (!shouldExempt(blockBreak.block, pos.y) && !pos.equals(lastBlock)) {
lastBlockIsSet = true;
lastBlockX = x;
lastBlockY = y;
lastBlockZ = z;
} else if (blockBreak.action == DiggingAction.CANCELLED_DIGGING) {
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
if (!shouldExempt(blockBreak.block, y) && notLastBlock) {
// https://github.com/GrimAnticheat/Grim/issues/1512
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && pos.equals(lastCancelledBlock))) {
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
boolean isLastCancelled = lastCancelledBlockIsSet && x == lastCancelledBlockX && y == lastCancelledBlockY && z == lastCancelledBlockZ;
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4) || (!lastBlockWasInstantBreak && isLastCancelled)) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=CANCELLED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -64,18 +77,20 @@ public class WrongBreak extends Check implements BlockBreakCheck {
}
}
lastCancelledBlock = pos;
lastLastBlock = null;
lastBlock = null;
return;
}
lastCancelledBlockIsSet = true;
lastCancelledBlockX = x;
lastCancelledBlockY = y;
lastCancelledBlockZ = z;
if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
final Vector3i pos = blockBreak.position;
lastLastBlockIsSet = lastBlockIsSet = false;
} else if (blockBreak.action == DiggingAction.FINISHED_DIGGING) {
boolean notLastCancelled = !lastCancelledBlockIsSet || x != lastCancelledBlockX || y != lastCancelledBlockY || z != lastCancelledBlockZ;
boolean notLastBlock = !lastBlockIsSet || x != lastBlockX || y != lastBlockY || z != lastBlockZ;
// when a player looks away from the mined block, they send a cancel, and if they look at it again, they don't send another start. (thanks mojang!)
if (!pos.equals(lastCancelledBlock) && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && !pos.equals(lastBlock)) {
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + MessageUtil.toUnlabledString(lastBlock) + ", pos=" + MessageUtil.toUnlabledString(pos))) {
if (notLastCancelled && (!lastBlockWasInstantBreak || player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) && notLastBlock) {
String lastBlockStr = lastBlockIsSet ? MessageUtil.toUnlabeledString(lastBlockX, lastBlockY, lastBlockZ) : "null";
if (flagAndAlert("action=FINISHED_DIGGING" + ", last=" + lastBlockStr + ", pos=" + MessageUtil.toUnlabeledString(x, y, z))) {
if (shouldModifyPackets()) {
blockBreak.cancel();
}
@@ -84,9 +99,7 @@ public class WrongBreak extends Check implements BlockBreakCheck {
// 1.14.4+ clients don't send another start break in protected regions
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_14_4)) {
lastCancelledBlock = null;
lastLastBlock = null;
lastBlock = null;
lastCancelledBlockIsSet = lastLastBlockIsSet = lastBlockIsSet = false;
}
}
}
@@ -14,7 +14,6 @@ import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientCh
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatCommandUnsigned;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientChatMessage;
// this can false from click events, but I doubt this would actually
// happen unless they're trying to flag, or if the server is set up badly
@CheckData(name = "ChatB", description = "Invalid chat message")
@@ -143,7 +143,10 @@ public class Reach extends Check implements PacketCheck {
} else {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) {
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
}
return ReachUtils.getMinReachToBox(player, targetBox) > player.compensatedEntities.self.getAttributeValue(Attributes.ENTITY_INTERACTION_RANGE);
}
@@ -175,7 +178,10 @@ public class Reach extends Check implements PacketCheck {
SimpleCollisionBox targetBox = reachEntity.getPossibleCollisionBoxes();
if (reachEntity.type == EntityTypes.END_CRYSTAL) { // Hardcode end crystal box
targetBox = new SimpleCollisionBox(reachEntity.trackedServerPosition.getPos().subtract(1, 0, 1), reachEntity.trackedServerPosition.getPos().add(1, 2, 1));
final double x = reachEntity.trackedServerPosition.getX();
final double y = reachEntity.trackedServerPosition.getX();
final double z = reachEntity.trackedServerPosition.getX();
targetBox = new SimpleCollisionBox(x - 1, y, z - 1, x + 1, y + 2, z + 1);
}
// 1.7 and 1.8 players get a bit of extra hitbox (this is why you should use 1.8 on cross version servers)
@@ -218,21 +224,27 @@ public class Reach extends Check implements PacketCheck {
// +3 would be 3 + 3 = 6, which is the pre-1.20.5 behaviour, preventing "Missed Hitbox"
final double distance = maxReach + 3;
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
final Vector3dm eyePos = new Vector3dm(from.getX(), 0, from.getZ());
final double eyePosX = from.getX();
final double eyePosZ = from.getZ();
for (Vector3dm lookVec : possibleLookDirs) {
for (double eye : possibleEyeHeights) {
eyePos.setY(from.getY() + eye);
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(lookVec.getX() * distance, lookVec.getY() * distance, lookVec.getZ() * distance));
final double eyePosY = from.getY() + eye;
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePos, endReachPos).first();
final double endX = eyePosX + lookVec.getX() * distance;
final double endY = eyePosY + lookVec.getY() * distance;
final double endZ = eyePosZ + lookVec.getZ() * distance;
if (ReachUtils.isVecInside(targetBox, eyePos)) {
Vector3dm intercept = ReachUtils.calculateIntercept(targetBox, eyePosX, eyePosY, eyePosZ, endX, endY, endZ).first();
if (ReachUtils.isVecInside(targetBox, eyePosX, eyePosY, eyePosZ)) {
minDistance = 0;
break;
}
if (intercept != null) {
minDistance = Math.min(eyePos.distance(intercept), minDistance);
minDistance = Math.min(intercept.distance(eyePosX, eyePosY, eyePosZ), minDistance);
}
}
}
@@ -10,7 +10,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraA", description = "Started gliding while already gliding", experimental = true)
@CheckData(name = "ElytraA", description = "Started gliding while already gliding")
public class ElytraA extends Check implements PostPredictionCheck {
private boolean setback;
@@ -9,7 +9,7 @@ import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraB", description = "Started gliding without jumping", experimental = true)
@CheckData(name = "ElytraB", description = "Started gliding without jumping")
public class ElytraB extends Check implements PostPredictionCheck {
private boolean glide;
private boolean setback;
@@ -11,7 +11,7 @@ import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.protocol.player.GameMode;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraC", description = "Started gliding too frequently", experimental = true)
@CheckData(name = "ElytraC", description = "Started gliding too frequently")
public class ElytraC extends Check implements PostPredictionCheck {
private boolean glideThisTick, glideLastTick, setback;
private int flags;
@@ -10,7 +10,7 @@ import com.github.retrooper.packetevents.protocol.packettype.PacketType;
import com.github.retrooper.packetevents.protocol.player.ClientVersion;
import com.github.retrooper.packetevents.wrapper.play.client.WrapperPlayClientEntityAction;
@CheckData(name = "ElytraF", description = "Started gliding while on ground", experimental = true)
@CheckData(name = "ElytraF", description = "Started gliding while on ground")
public class ElytraF extends Check implements PostPredictionCheck {
private boolean setback;
@@ -15,6 +15,8 @@ public class VehiclePredictionRunner extends Check implements VehicleCheck {
public void process(final VehiclePositionUpdate vehicleUpdate) {
// Vehicle onGround = false always
// We don't do vehicle setbacks because vehicle netcode sucks.
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(vehicleUpdate.from(), vehicleUpdate.to(), false, null, null, vehicleUpdate.isTeleport()));
player.movementCheckRunner.processAndCheckMovementPacket(new PositionUpdate(
vehicleUpdate.fromX(), vehicleUpdate.fromY(), vehicleUpdate.fromZ(), vehicleUpdate.toX(), vehicleUpdate.toY(), vehicleUpdate.toZ(),
false, null, null, vehicleUpdate.isTeleport()));
}
}
@@ -12,12 +12,13 @@ import org.jetbrains.annotations.NotNull;
import java.util.StringJoiner;
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving", experimental = true)
@CheckData(name = "MultiActionsC", description = "Clicked in inventory while moving")
public class MultiActionsC extends Check implements PacketCheck {
public MultiActionsC(GrimPlayer player) {
super(player);
}
// TODO: move this to a bett spot? not sure where to put this
@Contract(pure = true)
public static String getVerbose(@NotNull GrimPlayer player) {
StringJoiner verbose = new StringJoiner(", ");
@@ -7,7 +7,7 @@ import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving", experimental = true)
@CheckData(name = "MultiActionsD", description = "Closed inventory while moving")
public class MultiActionsD extends Check implements PacketCheck {
public MultiActionsD(GrimPlayer player) {
super(player);
@@ -42,24 +42,26 @@ public class DebugHandler extends AbstractDebugHandler implements PostPrediction
double offset = predictionComplete.getOffset();
if (listeners.isEmpty() && !outputToConsole) return;
if (player.predictedVelocity.vector.lengthSquared() == 0 && offset == 0) return;
final double predictedX = player.predictedVelocity.vectorX;
final double predictedY = player.predictedVelocity.vectorY;
final double predictedZ = player.predictedVelocity.vectorZ;
if (predictedX == 0.0 && predictedY == 0.0 && predictedZ == 0.0 && offset == 0) return;
String color = pickColor(offset, offset);
Vector3dm predicted = player.predictedVelocity.vector;
Vector3dm actually = player.actualMovement;
String xColor = pickColor(Math.abs(predicted.getX() - actually.getX()), offset);
String yColor = pickColor(Math.abs(predicted.getY() - actually.getY()), offset);
String zColor = pickColor(Math.abs(predicted.getZ() - actually.getZ()), offset);
String xColor = pickColor(Math.abs(predictedX - actually.getX()), offset);
String yColor = pickColor(Math.abs(predictedY - actually.getY()), offset);
String zColor = pickColor(Math.abs(predictedZ - actually.getZ()), offset);
Component p = Component.empty()
.append(P_PREFIX.color(NamedTextColor.NAMES.value(color)))
.append(Component.text(predicted.getX()).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.text(predictedX).color(NamedTextColor.NAMES.value(xColor)))
.append(Component.space())
.append(Component.text(predicted.getY()).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.text(predictedY).color(NamedTextColor.NAMES.value(yColor)))
.append(Component.space())
.append(Component.text(predicted.getZ()).color(NamedTextColor.NAMES.value(zColor)));
.append(Component.text(predictedZ).color(NamedTextColor.NAMES.value(zColor)));
Component a = Component.empty()
.append(A_PREFIX.color(NamedTextColor.NAMES.value(color)))
@@ -59,13 +59,16 @@ public class AirLiquidPlace extends BlockPlaceCheck {
if (player.gamemode == GameMode.CREATIVE) return;
Vector3i blockPos = place.position;
StateType placeAgainst = player.compensatedWorld.getBlockType(blockPos.getX(), blockPos.getY(), blockPos.getZ());
final int x = blockPos.getX();
final int y = blockPos.getY();
final int z = blockPos.getZ();
StateType placeAgainst = player.compensatedWorld.getBlockType(x, y, z);
int currentTick = GrimAPI.INSTANCE.getTickManager().currentTick;
// this is actual more lenient than we need to be, We can check up to 1 ticks for all changes at location sand up to 0 ticks for first change
// But for such tiny differences in legitness it's not worth it.
Iterable<BlockModification> blockModifications = player.blockHistory.getRecentModifications((blockModification) -> currentTick - blockModification.tick() < 2
&& blockPos.equals(blockModification.location())
&& x == blockModification.x() && y == blockModification.y() && z == blockModification.z()
&& (blockModification.cause() == BlockModification.Cause.START_DIGGING || blockModification.cause() == BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION));
// Check if old block from instant breaking in same tick as the current placement was valid
@@ -30,9 +30,8 @@ public class FarPlace extends BlockPlaceCheck {
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
for (double d : possibleEyeHeights) {
SimpleCollisionBox box = new SimpleCollisionBox(blockPos);
Vector3dm eyes = new Vector3dm(player.x, player.y + d, player.z);
Vector3dm best = VectorUtils.cutBoxToVector(eyes, box);
min = Math.min(min, eyes.distanceSquared(best));
Vector3dm best = VectorUtils.cutBoxToVector(player.x, player.y + d, player.z, box);
min = Math.min(min, best.distanceSquared(player.x, player.y + d, player.z));
}
// getPickRange() determines this?
@@ -112,10 +112,10 @@ public class RotationPlace extends BlockPlaceCheck {
for (double d : possibleEyeHeights) {
for (Vector3f lookDir : possibleLookDirs) {
// x, y, z are correct for the block placement even after post tick because of code elsewhere
Vector3d starting = new Vector3d(player.x, player.y + d, player.z);
// xRot and yRot are a tick behind
Ray trace = new Ray(player, starting.getX(), starting.getY(), starting.getZ(), lookDir.getX(), lookDir.getY());
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Ray trace = new Ray(player, player.x, player.y + d, player.z, lookDir.getX(), lookDir.getY());
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() != null) return true;
}
@@ -64,7 +64,7 @@ public class KnockbackHandler extends Check implements PostPredictionCheck {
// Blacklist problemated vector until mojang fixes a client-sided bug
if (playerVelocity.getY() == -0.04) {
velocity.setVelocity(playerVelocity.add(new Vector3d(0, 1 / 8000D, 0)));
velocity.setVelocity(playerVelocity.add(0, 1 / 8000D, 0));
playerVelocity = velocity.getVelocity();
event.markForReEncode(true);
}
@@ -483,7 +483,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.yaw = move.getYaw();
player.pitch = move.getPitch();
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp, position, move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
final VehiclePositionUpdate update = new VehiclePositionUpdate(clamp.getX(), clamp.getY(), clamp.getZ(),
position.getX(), position.getY(), position.getZ(), move.getYaw(), move.getPitch(), player.packetStateData.lastPacketWasTeleport);
player.checkManager.onVehiclePositionUpdate(update);
player.packetStateData.receivedSteerVehicle = false;
@@ -532,14 +533,17 @@ public class CheckManagerListener extends PacketListenerAbstract {
player.onPacketCancel();
}
Vector3i facePos = new Vector3i(packet.getBlockPosition().getX() + packet.getFace().getModX(), packet.getBlockPosition().getY() + packet.getFace().getModY(), packet.getBlockPosition().getZ() + packet.getFace().getModZ());
// Ends the client prediction introduced in 1.19+
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_19) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
player.user.sendPacket(new WrapperPlayServerAcknowledgeBlockChanges(packet.getSequence()));
} else { // The client isn't smart enough to revert changes
player.resyncPosition(packet.getBlockPosition());
player.resyncPosition(facePos);
final Vector3i blockPosition = packet.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.resyncPosition(x, y ,z);
final BlockFace face = packet.getFace();
player.resyncPosition(x + face.getModX(), y + face.getModY(), z + face.getModZ());
}
// Stop inventory desync from cancelling place
@@ -724,9 +728,8 @@ public class CheckManagerListener extends PacketListenerAbstract {
}
if (hasPosition) {
Vector3d position = new Vector3d(x, y, z);
Vector3d clampVector = VectorUtils.clampVector(position);
final PositionUpdate update = new PositionUpdate(new Vector3d(player.x, player.y, player.z), position, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
Vector3d clampVector = VectorUtils.clampVector(x, y, z);
final PositionUpdate update = new PositionUpdate(player.x, player.y, player.z, x, y ,z, onGround, teleportData.getSetback(), teleportData.getTeleportData(), teleportData.isTeleport());
// Stupidity doesn't care about 0.03
if (!player.packetStateData.lastPacketWasOnePointSeventeenDuplicate) {
@@ -770,14 +773,15 @@ public class CheckManagerListener extends PacketListenerAbstract {
return;
}
final BlockBreak blockBreak = new BlockBreak(player, packet.getBlockPosition(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
final Vector3i blockPosition = packet.getBlockPosition();
final BlockBreak blockBreak = new BlockBreak(player, blockPosition.getX(), blockPosition.getY(), blockPosition.getZ(), packet.getBlockFace(), packet.getBlockFaceId(), action, packet.getSequence(), player.compensatedWorld.getBlock(packet.getBlockPosition()));
player.checkManager.onBlockBreak(blockBreak);
if (blockBreak.isCancelled()) {
event.setCancelled(true);
player.onPacketCancel();
player.resyncPosition(blockBreak.position, packet.getSequence());
player.resyncPosition(blockBreak.x, blockBreak.y, blockBreak.z, packet.getSequence());
return;
}
@@ -785,7 +789,7 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (action == DiggingAction.FINISHED_DIGGING && BREAKABLE.apply(blockBreak.block.getType())) {
player.compensatedWorld.startPredicting();
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.updateBlock(blockBreak.x, blockBreak.y, blockBreak.z, 0);
player.compensatedWorld.stopPredicting(packet);
}
@@ -795,11 +799,14 @@ public class CheckManagerListener extends PacketListenerAbstract {
// Instant breaking, no damage means it is unbreakable by creative players (with swords)
if (damage >= 1) {
player.compensatedWorld.startPredicting();
final int x = blockBreak.x;
final int y = blockBreak.y;
final int z = blockBreak.z;
player.blockHistory.add(
new BlockModification(
player.compensatedWorld.getBlock(blockBreak.position),
player.compensatedWorld.getBlock(x, y, z),
WrappedBlockState.getByGlobalId(0),
blockBreak.position,
x, y, z,
GrimAPI.INSTANCE.getTickManager().currentTick,
BlockModification.Cause.START_DIGGING
)
@@ -807,9 +814,9 @@ public class CheckManagerListener extends PacketListenerAbstract {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && Materials.isWaterSource(player.getClientVersion(), blockBreak.block)) {
// Vanilla uses a method to grab water flowing, but as you can't break flowing water
// We can simply treat all waterlogged blocks or source blocks as source blocks
player.compensatedWorld.updateBlock(blockBreak.position, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion));
player.compensatedWorld.updateBlock(x, y, z, StateTypes.WATER.createBlockState(CompensatedWorld.blockVersion).getGlobalId());
} else {
player.compensatedWorld.updateBlock(blockBreak.position.x, blockBreak.position.y, blockBreak.position.z, 0);
player.compensatedWorld.updateBlock(x, y, z, 0);
}
player.compensatedWorld.stopPredicting(packet);
}
@@ -2,10 +2,9 @@ package ac.grim.grimac.events.packets;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.RotationData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
import com.github.retrooper.packetevents.event.PacketListenerPriority;
@@ -35,22 +34,26 @@ public class PacketServerTeleport extends PacketListenerAbstract {
WrapperPlayServerPlayerPositionAndLook teleport = new WrapperPlayServerPlayerPositionAndLook(event);
Vector3d pos = new Vector3d(teleport.getX(), teleport.getY(), teleport.getZ());
double posX = teleport.getX();
double posY = teleport.getY();
double posZ = teleport.getZ();
final float yaw = teleport.getYaw();
final float pitch = teleport.getPitch();
// This is the first packet sent to the client which we need to track
if (player.getSetbackTeleportUtil().getRequiredSetBack() == null) {
// Player teleport event gets called AFTER player join event
player.x = teleport.getX();
player.y = teleport.getY();
player.z = teleport.getZ();
player.yaw = teleport.getYaw();
player.pitch = teleport.getPitch();
player.x = posX;
player.y = posY;
player.z = posZ;
player.yaw = yaw;
player.pitch = pitch;
player.lastX = teleport.getX();
player.lastY = teleport.getY();
player.lastZ = teleport.getZ();
player.lastYaw = teleport.getYaw();
player.lastPitch = teleport.getPitch();
player.lastX = posX;
player.lastY = posY;
player.lastZ = posZ;
player.lastYaw = yaw;
player.lastPitch = pitch;
player.pollData();
}
@@ -67,24 +70,24 @@ public class PacketServerTeleport extends PacketListenerAbstract {
relativeZ = teleport.isRelativeFlag(RelativeFlag.Z);
if (relativeX) {
pos = pos.add(new Vector3d(player.x, 0, 0));
posX += player.x;
teleport.setRelative(RelativeFlag.X, false);
}
if (relativeY) {
pos = pos.add(new Vector3d(0, player.y, 0));
posY += player.y;
teleport.setRelative(RelativeFlag.Y, false);
}
if (relativeZ) {
pos = pos.add(new Vector3d(0, 0, player.z));
posZ += player.z;
teleport.setRelative(RelativeFlag.Z, false);
}
if (relativeX || relativeY || relativeZ) {
teleport.setX(pos.getX());
teleport.setY(pos.getY());
teleport.setZ(pos.getZ());
teleport.setX(posX);
teleport.setY(posY);
teleport.setZ(posZ);
event.markForReEncode(true);
}
@@ -124,10 +127,13 @@ public class PacketServerTeleport extends PacketListenerAbstract {
// For some reason teleports on 1.7 servers are offset by 1.62?
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThan(ServerVersion.V_1_8))
pos = pos.withY(pos.getY() - 1.62);
posY -= 1.62;
Location target = new Location(null, pos.getX(), pos.getY(), pos.getZ());
player.getSetbackTeleportUtil().addSentTeleport(target, teleport.getDeltaMovement(), lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId());
final Vector3d deltaMovement = teleport.getDeltaMovement();
player.getSetbackTeleportUtil().addSentTeleport(posX, posY, posZ,
deltaMovement.getX(), deltaMovement.getY(), deltaMovement.getZ(),
lastTransactionSent, teleport.getRelativeFlags(), true, teleport.getTeleportId()
);
}
if (event.getPacketType() == PacketType.Play.Server.PLAYER_ROTATION) {
@@ -157,9 +163,12 @@ public class PacketServerTeleport extends PacketListenerAbstract {
player.sendTransaction();
event.getTasksAfterSend().add(player::sendTransaction);
player.vehicleData.vehicleTeleports.add(new Pair<>(
Vector3d position = new WrapperPlayServerVehicleMove(event).getPosition();
player.vehicleData.vehicleTeleports.add(new VehicleData.VehicleTeleport(
player.lastTransactionSent.get(),
new WrapperPlayServerVehicleMove(event).getPosition()
position.getX(),
position.getY(),
position.getZ()
));
}
}
@@ -77,7 +77,11 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (player == null) return;
WrapperPlayServerAcknowledgePlayerDigging ack = new WrapperPlayServerAcknowledgePlayerDigging(event);
player.compensatedWorld.handleBlockBreakAck(ack.getBlockPosition(), ack.getBlockId(), ack.getAction(), ack.isSuccessful());
Vector3i blockPosition = ack.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
player.compensatedWorld.handleBlockBreakAck(x, y, z, ack.getBlockId(), ack.getAction(), ack.isSuccessful());
}
if (event.getPacketType() == PacketType.Play.Server.CHANGE_GAME_STATE) {
@@ -121,7 +125,7 @@ public class BasePacketWorldReader extends PacketListenerAbstract {
if (teleports.getFlags().getMask() != 0) {
continue; // Worse that will happen is people will get an extra setback...
}
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocation().getX() - chunkCenterX) < 16 && Math.abs(teleports.getLocation().getZ() - chunkCenterZ) < 16);
shouldPostTrans = shouldPostTrans || (Math.abs(teleports.getLocationX() - chunkCenterX) < 16 && Math.abs(teleports.getLocationZ() - chunkCenterZ) < 16);
}
if (shouldPostTrans) {
@@ -116,6 +116,7 @@ public class CheckManager {
.put(BadPacketsU.class, new BadPacketsU(player))
.put(BadPacketsV.class, new BadPacketsV(player))
.put(BadPacketsY.class, new BadPacketsY(player))
.put(BadPacketsZ.class, new BadPacketsZ(player))
.put(MultiActionsA.class, new MultiActionsA(player))
.put(MultiActionsC.class, new MultiActionsC(player))
.put(MultiActionsD.class, new MultiActionsD(player))
@@ -18,9 +18,9 @@ import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.TeleportAcceptData;
import ac.grim.grimac.utils.data.TeleportData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.VehicleData;
import ac.grim.grimac.utils.data.VelocityData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -74,7 +74,9 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
@Override
public void onPredictionComplete(final PredictionComplete predictionComplete) {
// Grab friction now when we know player on ground and other variables
Vector3dm afterTickFriction = player.clientVelocity.clone();
final double afterTickFrictionX = player.clientVelocity.getX();
final double afterTickFrictionY = player.clientVelocity.getY();
final double afterTickFrictionZ = player.clientVelocity.getZ();
// We must first check if the player has accepted their setback
// If the setback isn't complete, then this position is illegitimate
@@ -82,12 +84,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// The player needs to now wait for their vehicle to go into the right place before getting back in
if (cheatVehicleInterpolationDelay > 0) cheatVehicleInterpolationDelay = 10;
// Teleport, let velocity be reset
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
} else if (requiredSetBack == null || requiredSetBack.isComplete()) {
cheatVehicleInterpolationDelay--;
// No simulation... we can do that later. We just need to know the valid position.
// As we didn't setback here, the new position is known to be safe!
lastKnownGoodPosition = new SetbackPosWithVector(new Vector3d(player.x, player.y, player.z), afterTickFriction);
lastKnownGoodPosition = new SetbackPosWithVector(player.x, player.y, player.z, afterTickFrictionX, afterTickFrictionY, afterTickFrictionZ);
}
if (requiredSetBack != null) requiredSetBack.tick();
@@ -123,27 +125,6 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
return player.platformPlayer != null && player.noSetbackPermission;
}
private void simulateFriction(Vector3dm vector) {
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector.multiply(0.5D);
if (player.hasGravity)
vector.add(new Vector3dm(0.0D, -player.gravity / 4.0D, 0.0D));
} else if (player.isGliding) {
PredictionEngineElytra.getElytraMovement(player, vector, ReachUtils.getLook(player, player.yaw, player.pitch)).multiply(player.stuckSpeedMultiplier).multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
vector.setY(vector.getY() - 0.05); // Make the player fall a bit
} else { // Gliding doesn't have friction, we handle it differently
PredictionEngineNormal.staticVectorEndOfTick(player, vector); // Lava and normal movement
vector.multiply(player.stuckSpeedMultiplier); // Prevent abusing setbacks to move out of blocks like webs
}
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector, VectorData.VectorType.BestVelPicked))));
}
private void blockMovementsUntilResync(boolean simulateNextTickPosition, boolean isResync) {
if (requiredSetBack == null) return; // Hasn't spawned
if (player.platformPlayer != null && player.noSetbackPermission)
@@ -157,64 +138,115 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
lastWorldResync = System.currentTimeMillis();
}
Vector3dm clientVel = lastKnownGoodPosition.vector.clone();
double clientVelX = lastKnownGoodPosition.getVectorX();
double clientVelY = lastKnownGoodPosition.getVectorY();
double clientVelZ = lastKnownGoodPosition.getVectorZ();
Pair<VelocityData, Vector3dm> futureKb = player.checkManager.getKnockbackHandler().getFutureKnockback();
VelocityData futureExplosion = player.checkManager.getExplosionHandler().getFutureExplosion();
// Velocity sets
if (futureKb.first() != null) {
clientVel = futureKb.second();
clientVelX = futureKb.second().getX();
clientVelY = futureKb.second().getY();
clientVelZ = futureKb.second().getZ();
}
// Explosion adds
if (futureExplosion != null && (futureKb.first() == null || futureKb.first().transaction < futureExplosion.transaction)) {
clientVel.add(futureExplosion.vector);
clientVelX += futureExplosion.vector.getX();
clientVelY += futureExplosion.vector.getY();
clientVelZ += futureExplosion.vector.getZ();
}
Vector3d position = lastKnownGoodPosition.pos;
double x = lastKnownGoodPosition.posX;
double y = lastKnownGoodPosition.posY;
double z = lastKnownGoodPosition.posZ;
SimpleCollisionBox oldBB = player.boundingBox;
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, position.getX(), position.getY(), position.getZ());
player.boundingBox = GetBoundingBox.getPlayerBoundingBox(player, x, y, z);
// Mini prediction engine - simulate collisions
if (simulateNextTickPosition) {
Vector3dm collide = Collisions.collide(player, clientVel.getX(), clientVel.getY(), clientVel.getZ());
Vector3dm collide = Collisions.collide(player, clientVelX, clientVelY, clientVelZ);
position = position.withX(position.getX() + collide.getX());
position = position.withY(position.getY() + collide.getY());
x += collide.getX();
y += collide.getY();
// TODO: Is this even needed? Can't reproduce any phasing on vanilla 1.8 when being setback.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
// 1.8 players need the collision epsilon to not phase into blocks when being setback
// Due to simulation, this will not allow a flight bypass by sending a billion invalid movements
position = position.withY(position.getY() + SimpleCollisionBox.COLLISION_EPSILON);
y += SimpleCollisionBox.COLLISION_EPSILON;
}
position = position.withZ(position.getZ() + collide.getZ());
z += collide.getZ();
if (clientVel.getX() != collide.getX()) clientVel.setX(0);
if (clientVel.getY() != collide.getY()) clientVel.setY(0);
if (clientVel.getZ() != collide.getZ()) clientVel.setZ(0);
if (clientVelX != collide.getX()) clientVelX = 0;
if (clientVelY != collide.getY()) clientVelY = 0;
if (clientVelZ != collide.getZ()) clientVelZ = 0;
simulateFriction(clientVel);
// BEGIN SIMULATING FRICTION
// We must always do this before simulating positions, as this is the last actual (safe) movement
// We must not do this for knockback or explosions, as they are at the start of the tick
double[] vector = {clientVelX, clientVelY, clientVelZ};
if (player.wasTouchingWater) {
PredictionEngineWater.staticVectorEndOfTick(player, vector, 0.8F, player.gravity, true);
} else if (player.wasTouchingLava) {
vector[0] *= 0.5D;
vector[1] *= 0.5D;
vector[2] *= 0.5D;
if (player.hasGravity)
vector[1] += -player.gravity / 4.0D;
} else if (player.isGliding) {
Vector3dm getLook = ReachUtils.getLook(player, player.yaw, player.pitch);
PredictionEngineElytra.applyElytraMovementInPlace(player, vector, getLook.getX(), getLook.getY(), getLook.getZ());
vector[0] *= player.stuckSpeedMultiplier.getX() * 0.99F;
vector[1] *= player.stuckSpeedMultiplier.getY() * 0.98F;
vector[2] *= player.stuckSpeedMultiplier.getZ() * 0.99F;
vector[1] -= 0.05; // Make the player fall a bit
} else { // Gliding doesn't have friction, we handle it differently
// Lava and normal movement
vector[0] *= player.friction;
vector[1] = PredictionEngineNormal.staticVectorEndOfTickY(player, vector[1]);
vector[2] *= player.friction;
// Prevent abusing setbacks to move out of blocks like webs
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
// stop 1.8 players from stepping onto 1.25 high blocks, because why not?
new PredictionEngine().applyMovementThreshold(player, new HashSet<>(Collections.singletonList(new VectorData(vector[0], vector[1], vector[2], VectorData.VectorType.BestVelPicked))));
// END SIMULATING FRICTION
clientVelX = vector[0];
clientVelY = vector[1];
clientVelZ = vector[2];
}
player.boundingBox = oldBB; // reset back to the new bounding box
boolean sendVelocity = true;
if (!hasAcceptedSpawnTeleport || player.isFlying)
clientVel = null; // if the player is flying or hasn't spawned... don't force kb
sendVelocity = false; // if the player is flying or hasn't spawned... don't force kb
// Something weird has occurred in the player's movement, block offsets until we resync
if (isResync) {
blockOffsets = true;
}
SetBackData data = new SetBackData(new TeleportData(position, new Vector3d(), new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVel, player.inVehicle(), false);
SetBackData data = new SetBackData(new TeleportData(x, y, z, 0, 0, 0, new RelativeFlag(0b11000), player.lastTransactionSent.get(), 0), player.yaw, player.pitch, clientVelX, clientVelY, clientVelZ, sendVelocity, player.inVehicle(), false);
sendSetback(data);
}
private void sendSetback(SetBackData data) {
isSendingSetback = true;
Vector3d position = data.getTeleportData().getLocation();
double positionX = data.getTeleportData().getLocationX();
double positionY = data.getTeleportData().getLocationY();
double positionZ = data.getTeleportData().getLocationZ();
try {
// Player is in a vehicle
@@ -230,7 +262,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
// Stop the player from being able to teleport vehicles and simply re-enter them to continue,
// therefore, teleport the entity
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(position.getX(), position.getY(), position.getZ()), player.yaw % 360, 0, false));
player.user.sendPacket(new WrapperPlayServerEntityTeleport(vehicleId, new Vector3d(positionX, positionY, positionZ), player.yaw % 360, 0, false));
player.getSetbackTeleportUtil().cheatVehicleInterpolationDelay = Integer.MAX_VALUE; // Set to max until player accepts the new position
// Make sure bukkit also knows the player got teleported out of their vehicle, can't do this async
@@ -245,7 +277,7 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
}
}
double y = position.getY();
double y = positionY;
if (PacketEvents.getAPI().getServerManager().getVersion().isOlderThanOrEquals(ServerVersion.V_1_7_10)) {
y += 1.62; // 1.7 teleport offset if grim ever supports 1.7 again
}
@@ -260,15 +292,15 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
data.getTeleportData().setTransaction(player.lastTransactionSent.get());
// Use provided transaction ID to make sure it can never desync, although there's no reason to do this
addSentTeleport(new Location(null, position.getX(), y, position.getZ(), player.yaw % 360, player.pitch % 360), new Vector3d(), data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
addSentTeleport(positionX, y, positionZ, 0, 0, 0, data.getTeleportData().getTransaction(), new RelativeFlag(0b11000), false, teleportId);
// This must be done after setting the sent teleport, otherwise we lose velocity data
requiredSetBack = data;
// Send after tracking to fix race condition
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(position.getX(), position.getY(), position.getZ(), 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
PacketEvents.getAPI().getProtocolManager().sendPacketSilently(player.user.getChannel(), new WrapperPlayServerPlayerPositionAndLook(positionX, positionY, positionZ, 0, 0, data.getTeleportData().getFlags().getMask(), teleportId, false));
player.sendTransaction();
if (data.getVelocity() != null && data.getVelocity().lengthSquared() > 0) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocity().getX(), data.getVelocity().getY(), data.getVelocity().getZ())));
if (data.isHasVelocity() && (data.getVelocityX() != 0 || data.getVelocityY() != 0 || data.getVelocityZ() != 0)) {
player.user.sendPacket(new WrapperPlayServerEntityVelocity(player.entityID, new Vector3d(data.getVelocityX(), data.getVelocityY(), data.getVelocityZ())));
}
} finally {
isSendingSetback = false;
@@ -288,12 +320,12 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
TeleportData teleportPos;
while ((teleportPos = pendingTeleports.peek()) != null) {
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocation().getX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocation().getY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocation().getZ();
double trueTeleportX = (teleportPos.isRelativeX() ? player.x : 0) + teleportPos.getLocationX();
double trueTeleportY = (teleportPos.isRelativeY() ? player.y : 0) + teleportPos.getLocationY();
double trueTeleportZ = (teleportPos.isRelativeZ() ? player.z : 0) + teleportPos.getLocationZ();
// There seems to be a version difference in teleports past 30 million... just clamp the vector
Vector3d clamped = VectorUtils.clampVector(new Vector3d(trueTeleportX, trueTeleportY, trueTeleportZ));
Vector3d clamped = VectorUtils.clampVector(trueTeleportX, trueTeleportY, trueTeleportZ);
double threshold = teleportPos.isRelativePos() ? player.getMovementThreshold() : 0;
boolean closeEnoughY = Math.abs(clamped.getY() - y) <= 1e-7 + threshold; // 1.7 rounding
@@ -339,18 +371,17 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
int lastTransaction = player.lastTransactionReceived.get();
while (true) {
Pair<Integer, Vector3d> teleportPos = player.vehicleData.vehicleTeleports.peek();
VehicleData.VehicleTeleport teleportPos = player.vehicleData.vehicleTeleports.peek();
if (teleportPos == null) break;
if (lastTransaction < teleportPos.first()) {
if (lastTransaction < teleportPos.teleportId()) {
break;
}
Vector3d position = teleportPos.second();
if (position.getX() == x && position.getY() == y && position.getZ() == z) {
if (teleportPos.x() == x && teleportPos.y() == y && teleportPos.z() == z) {
player.vehicleData.vehicleTeleports.poll();
return true;
} else if (lastTransaction > teleportPos.first() + 1) {
} else if (lastTransaction > teleportPos.teleportId() + 1) {
player.vehicleData.vehicleTeleports.poll();
// Vehicles have terrible netcode so just ignore it if the teleport wasn't from us setting the player back
@@ -396,36 +427,37 @@ public class SetbackTeleportUtil extends Check implements PostPredictionCheck {
!player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport);
}
public void addSentTeleport(Location position, Vector3d velocity, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(new Vector3d(position.getX(), position.getY(), position.getZ()), velocity, flags, transaction, teleportId);
public void addSentTeleport(double x, double y, double z, double velocityX, double velocityY, double velocityZ, int transaction, RelativeFlag flags, boolean plugin, int teleportId) {
TeleportData data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, flags, transaction, teleportId);
pendingTeleports.add(data);
Vector3d safePosition = new Vector3d(position.getX(), position.getY(), position.getZ());
// We must convert relative teleports to avoid them becoming client controlled in the case of setback
if (flags.has(RelativeFlag.X)) {
safePosition = safePosition.withX(safePosition.getX() + lastKnownGoodPosition.pos.getX());
x += lastKnownGoodPosition.posX;
}
if (flags.has(RelativeFlag.Y)) {
safePosition = safePosition.withY(safePosition.getY() + lastKnownGoodPosition.pos.getY());
y += lastKnownGoodPosition.posY;
}
if (flags.has(RelativeFlag.Z)) {
safePosition = safePosition.withZ(safePosition.getZ() + lastKnownGoodPosition.pos.getZ());
z += lastKnownGoodPosition.posZ;
}
data = new TeleportData(safePosition, velocity, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, null, false, plugin);
data = new TeleportData(x, y, z, velocityX, velocityY, velocityZ, new RelativeFlag(0b11000), transaction, teleportId);
requiredSetBack = new SetBackData(data, player.yaw, player.pitch, false, plugin);
this.lastKnownGoodPosition = new SetbackPosWithVector(safePosition, new Vector3dm());
this.lastKnownGoodPosition = new SetbackPosWithVector(x, y, z, 0, 0, 0);
}
@AllArgsConstructor
@Getter
@Setter
public static class SetbackPosWithVector {
private final Vector3d pos;
private Vector3dm vector;
private final double posX;
private final double posY;
private final double posZ;
private final double vectorY;
private double vectorX, vectorZ;
}
}
@@ -6,10 +6,12 @@ import ac.grim.grimac.checks.type.PostPredictionCheck;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.predictionengine.UncertaintyHandler;
import ac.grim.grimac.predictionengine.predictions.PredictionEngine;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.lists.EvictingQueue;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2;
import ac.grim.grimac.utils.math.Vector3dm;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@@ -54,7 +56,12 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
for (Iterator<Object2IntMap.Entry<StringBuilder>> it = continuedDebug.object2IntEntrySet().iterator(); it.hasNext(); ) {
Map.Entry<StringBuilder, Integer> debug = it.next();
appendDebug(debug.getKey(), player.predictedVelocity, player.actualMovement, location, player.startTickClientVel, player.baseTickAddition, player.baseTickWaterPushing);
appendDebug(debug.getKey(), player.predictedVelocity,
player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(),
location,
player.startTickClientVel.getX(), player.startTickClientVel.getY(), player.startTickClientVel.getZ(),
player.baseTickAddition.getX(), player.baseTickAddition.getY(), player.baseTickAddition.getZ(),
player.baseTickWaterPushing.getX(), player.baseTickWaterPushing.getY(), player.baseTickWaterPushing.getZ());
debug.setValue(debug.getValue() - 1);
if (debug.getValue() <= 0) it.remove();
}
@@ -90,7 +97,14 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
Vector3dm startTickVel = startTickClientVel.get(i);
Vector3dm addition = baseTickAddition.get(i);
Vector3dm water = baseTickWater.get(i);
appendDebug(sb, predict, actual, loc, startTickVel, addition, water);
appendDebug(sb,
predict,
actual.getX(), actual.getY(), actual.getZ(),
loc,
startTickVel.getX(), startTickVel.getY(), startTickVel.getZ(),
addition.getX(), addition.getY(), addition.getZ(),
water.getX(), water.getY(), water.getZ()
);
}
UncertaintyHandler uncertaintyHandler = player.uncertaintyHandler;
@@ -194,7 +208,13 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
continuedDebug.put(sb, 40);
}
private void appendDebug(StringBuilder sb, VectorData predict, Vector3dm actual, Location location, Vector3dm startTick, Vector3dm addition, Vector3dm water) {
private void appendDebug(final StringBuilder sb, final VectorData predict,
final double actualX, final double actualY, final double actualZ,
final Location location,
final double startTickX, final double startTickY, final double startTickZ,
final double additionX, final double additionY, final double additionZ,
final double waterX, final double waterY, final double waterZ) {
if (predict.isZeroPointZeroThree()) {
sb.append("Movement threshold/tick skipping\n");
}
@@ -228,36 +248,45 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
}
// Apply 0.003/0.005 to make numbers more accurate
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTick.clone(), VectorData.VectorType.BestVelPicked)));
Set<VectorData> set = new HashSet<>(Collections.singletonList(new VectorData(startTickX, startTickY, startTickZ, VectorData.VectorType.BestVelPicked)));
new PredictionEngine().applyMovementThreshold(player, set);
Vector3dm trueStartVel = ((VectorData) set.toArray()[0]).vector;
VectorData tempVectorData = ((VectorData) set.toArray()[0]);
double trueStartVelX = tempVectorData.vectorX;
double trueStartVelY = tempVectorData.vectorY;
double trueStartVelZ = tempVectorData.vectorZ;
Vector3dm clientMovement = getPlayerMathMovement(player, actual.clone().subtract(trueStartVel), location.xRot);
Vector3dm simulatedMovement = getPlayerMathMovement(player, predict.vector.clone().subtract(trueStartVel), location.xRot);
Vector3dm offset = actual.clone().subtract(predict.vector);
trueStartVel.add(addition);
trueStartVel.add(water);
final double offsetX = actualX - trueStartVelX;
final double offsetY = actualY - trueStartVelY;
final double offsetZ = actualZ - trueStartVelZ;
double offsetLength = Math.sqrt(offsetX*offsetX + offsetY*offsetY + offsetZ*offsetZ);
Vec2 clientMovement = getPlayerMathMovement(player, offsetX, offsetZ, location.xRot);
Vec2 simulatedMovement = getPlayerMathMovement(player, predict.vectorX - trueStartVelX, predict.vectorZ - trueStartVelZ, location.xRot);
trueStartVelX += additionX + waterX;
trueStartVelY += additionY + waterY;
trueStartVelZ += additionZ + waterZ;
sb.append("Simulated: ");
sb.append(predict.vector.toString());
sb.append(MessageUtil.toUnlabeledString(trueStartVelX, trueStartVelY, trueStartVelZ));
sb.append("\nActually: ");
sb.append(actual);
sb.append(MessageUtil.toUnlabeledString(actualX, actualY, actualZ));
sb.append("\nOffset Vector: ");
sb.append(offset);
sb.append(MessageUtil.toUnlabeledString(offsetX, offsetY, offsetZ));
sb.append("\nOffset: ");
sb.append(offset.length());
sb.append(offsetLength);
sb.append("\nLocation: ");
sb.append(location);
sb.append("\nInitial velocity: ");
sb.append(startTick);
sb.append(MessageUtil.toUnlabeledString(startTickX, startTickY, startTickZ));
if (addition.lengthSquared() > 0) {
if (additionX != 0 || additionY != 0 || additionZ != 0) {
sb.append("\nInitial vel addition: ");
sb.append(addition);
sb.append(MessageUtil.toUnlabeledString(additionX, additionY, additionZ));
}
if (water.lengthSquared() > 0) {
if (waterX != 0 || waterY != 0 || waterZ != 0) {
sb.append("\nWater vel addition: ");
sb.append(water);
sb.append(MessageUtil.toUnlabeledString(waterX, waterY, waterZ));
}
sb.append("\nClient input: ");
@@ -273,14 +302,14 @@ public final class SuperDebug extends Check implements PostPredictionCheck {
sb.append("\n\n");
}
private Vector3dm getPlayerMathMovement(GrimPlayer player, Vector3dm wantedMovement, float f2) {
private Vec2 getPlayerMathMovement(GrimPlayer player, double x, double z, float f2) {
float f3 = player.trigHandler.sin(f2 * 0.017453292f);
float f4 = player.trigHandler.cos(f2 * 0.017453292f);
float bestTheoreticalX = (float) (f3 * wantedMovement.getZ() + f4 * wantedMovement.getX()) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * wantedMovement.getX() + f4 * wantedMovement.getZ()) / (f3 * f3 + f4 * f4);
float bestTheoreticalX = (float) (f3 * z + f4 * x) / (f3 * f3 + f4 * f4);
float bestTheoreticalZ = (float) (-f3 * x + f4 * z) / (f3 * f3 + f4 * f4);
return new Vector3dm(bestTheoreticalX, 0, bestTheoreticalZ);
return new Vec2(bestTheoreticalX, bestTheoreticalZ);
}
private record Location(double x, double y, double z, float xRot, float yRot, String world) {
@@ -7,7 +7,6 @@ import ac.grim.grimac.platform.api.world.PlatformWorld;
import ac.grim.grimac.player.GrimPlayer;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerAcknowledgeBlockChanges;
import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerBlockChange;
@@ -116,7 +115,7 @@ public class DefaultResyncHandler implements ResyncHandler {
GrimAPI.INSTANCE.getScheduler().getRegionScheduler().execute(GrimAPI.INSTANCE.getGrimPlugin(), world, chunkX, chunkZ, () -> {
if (!player.platformPlayer.isOnline() || !player.getSetbackTeleportUtil().hasAcceptedSpawnTeleport)
return;
if (player.platformPlayer.getPosition().distance(new Vector3d(x, y, z)) >= 64)
if (player.platformPlayer.distanceSquared(x, y, z) >= 64 * 64)
return;
if (!world.isChunkLoaded(chunkX, chunkZ)) return; // Don't load chunks sync
@@ -25,4 +25,6 @@ public interface GrimEntity extends GrimIdentity {
PlatformWorld getWorld();
Location getLocation();
double distanceSquared(double x, double y, double z);
}
@@ -364,12 +364,15 @@ public class GrimPlayer implements GrimUser {
// It's very difficult to test precedence so if there's issues with this bouncy implementation let me know
for (VectorData data : new HashSet<>(possibleMovements)) {
for (BlockFace direction : uncertaintyHandler.slimePistonBounces) {
final double x = data.vectorX;
final double y = data.vectorY;
final double z = data.vectorZ;
if (direction.getModX() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setX(direction.getModX()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(direction.getModX(), y, z, VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModY() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setY(direction.getModY()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(x, direction.getModY(), z, VectorData.VectorType.SlimePistonBounce));
} else if (direction.getModZ() != 0) {
possibleMovements.add(data.returnNewModified(data.vector.clone().setZ(direction.getModZ()), VectorData.VectorType.SlimePistonBounce));
possibleMovements.add(data.returnNewModified(x, y, direction.getModZ(), VectorData.VectorType.SlimePistonBounce));
}
}
}
@@ -796,7 +799,10 @@ public class GrimPlayer implements GrimUser {
}
public boolean isInWaterOrRain() {
return compensatedWorld.isRaining || Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f), (block) -> Materials.isWater(CompensatedWorld.blockVersion, block.first()));
return compensatedWorld.isRaining
|| Collisions.hasMaterial(this, boundingBox.copy().expand(0.1f),
(block, x, y, z) -> Materials.isWater(CompensatedWorld.blockVersion, block)
);
}
@Contract(pure = true)
@@ -925,12 +931,12 @@ public class GrimPlayer implements GrimUser {
if (platformPlayer != null) platformPlayer.sendMessage(message);
}
public void resyncPosition(Vector3i pos) {
this.resyncHandler.resync(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ());
public void resyncPosition(final int x, final int y, final int z) {
this.resyncHandler.resync(x, y, z, x, y, z);
}
public void resyncPosition(Vector3i pos, int sequenceID) {
this.resyncHandler.resyncPosition(pos.x, pos.y, pos.z, sequenceID);
public void resyncPosition(final int x, final int y, final int z, final int sequenceID) {
this.resyncHandler.resyncPosition(x, y, z, sequenceID);
}
public void resyncPositions(SimpleCollisionBox box) {
@@ -18,6 +18,7 @@ import ac.grim.grimac.predictionengine.predictions.rideable.PredictionEngineBoat
import ac.grim.grimac.utils.anticheat.update.PositionUpdate;
import ac.grim.grimac.utils.anticheat.update.PredictionComplete;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.SetBackData;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
@@ -196,7 +197,6 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.vehicleData.wasVehicleSwitch = false;
if (riding != null) {
Vector3dm pos = new Vector3dm(player.x, player.y, player.z);
SimpleCollisionBox interTruePositions = riding.getPossibleCollisionBoxes();
// We shrink the expanded bounding box to what the packet positions can be, for a smaller box
@@ -206,7 +206,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
interTruePositions.expand(-width, 0, -width);
interTruePositions.expandMax(0, -height, 0);
Vector3dm cutTo = VectorUtils.cutBoxToVector(pos, interTruePositions);
Vector3dm cutTo = VectorUtils.cutBoxToVector(player.x, player.y, player.z, interTruePositions);
// Now we need to simulate a tick starting at the most optimal position
// The start position is never sent, so we assume the most optimal start position
@@ -231,9 +231,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
handleTeleport(update);
if (player.isClimbing) {
Vector3dm ladder = player.clientVelocity.clone().setY(0.2);
PredictionEngineNormal.staticVectorEndOfTick(player, ladder);
player.lastWasClimbing = ladder.getY();
player.lastWasClimbing = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
}
return;
}
@@ -363,8 +361,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
}
SimpleCollisionBox steppingOnBB = GetBoundingBox.getCollisionBoxForPlayer(player, player.x, player.y, player.z).expand(player.getMovementThreshold()).offset(0, -1, 0);
Collisions.hasMaterial(player, steppingOnBB, (pair) -> {
WrappedBlockState data = pair.first();
Collisions.hasMaterial(player, steppingOnBB, (data) -> {
if (data.getType() == StateTypes.SLIME_BLOCK && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_8)) {
player.uncertaintyHandler.isSteppingOnSlime = true;
player.uncertaintyHandler.isSteppingOnBouncyBlock = true;
@@ -410,8 +407,8 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.uncertaintyHandler.isNearGlitchyBlock = player.getClientVersion().isOlderThan(ClientVersion.V_1_9)
&& Collisions.hasMaterial(player, expandedBB.copy().expand(0.2),
checkData -> BlockTags.ANVIL.contains(checkData.first().getType())
|| checkData.first().getType() == StateTypes.CHEST || checkData.first().getType() == StateTypes.TRAPPED_CHEST);
checkData -> BlockTags.ANVIL.contains(checkData.getType())
|| checkData.getType() == StateTypes.CHEST || checkData.getType() == StateTypes.TRAPPED_CHEST);
player.uncertaintyHandler.isOrWasNearGlitchyBlock = isGlitchy || player.uncertaintyHandler.isNearGlitchyBlock;
player.uncertaintyHandler.checkForHardCollision();
@@ -453,7 +450,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
player.clientVelocity = player.actualMovement.clone();
player.gravity = 0;
player.friction = 0.91f;
PredictionEngineNormal.staticVectorEndOfTick(player, player.clientVelocity);
player.clientVelocity.setX(player.clientVelocity.getX() * player.friction);
player.clientVelocity.setY(PredictionEngineNormal.staticVectorEndOfTickY(player, player.clientVelocity.getY()));
player.clientVelocity.setZ(player.clientVelocity.getZ() * player.friction);
} else if (riding == null) {
wasChecked = true;
@@ -522,7 +521,7 @@ public class MovementCheckRunner extends Check implements PositionCheck {
} // If it isn't any of these cases, the player is on a mob they can't control and therefore is exempt
// No, don't comment about the sqrt call. It doesn't matter unless you run sqrt thousands of times a second.
double offset = player.predictedVelocity.vector.distance(player.actualMovement);
double offset = player.actualMovement.distance(player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
offset = player.uncertaintyHandler.reduceOffset(offset);
if (player.packetStateData.tryingToRiptide != clientClaimsRiptide) {
@@ -535,19 +534,19 @@ public class MovementCheckRunner extends Check implements PositionCheck {
//
// Checking for oldClientVel being too high fixes BleachHack vertical scaffold
if (player.getSetbackTeleportUtil().getRequiredSetBack() != null && player.getSetbackTeleportUtil().getRequiredSetBack().getTicksComplete() == 1) {
Vector3dm setbackVel = player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity();
SetBackData setback = player.getSetbackTeleportUtil().getRequiredSetBack();
// A player must have velocity going INTO the ground to be able to jump
// Otherwise they could ignore upwards velocity that isn't useful into more useful upwards velocity (towering)
// So if they are supposed to be going upwards, or are supposed to be off the ground, resync
if (player.predictedVelocity.isJump()
&& !player.wasTouchingLava && !player.wasTouchingWater
&& ((setbackVel != null && setbackVel.getY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
&& ((setback.isHasVelocity() && setback.getVelocityY() >= 0) || !Collisions.slowCouldPointThreeHitGround(player, player.lastX, player.lastY, player.lastZ))) {
player.getSetbackTeleportUtil().executeForceResync();
}
boolean lavaBugFix = player.wasTouchingLava && player.predictedVelocity.isJump() &&
player.predictedVelocity.vector.getY() < 0.06 && player.predictedVelocity.vector.getY() > -0.02;
player.predictedVelocity.vectorY < 0.06 && player.predictedVelocity.vectorY > -0.02;
// Player ignored the knockback or is delaying it a tick... bad!
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().getVelocity() != null) {
if (!player.predictedVelocity.isKnockback() && !lavaBugFix && player.getSetbackTeleportUtil().getRequiredSetBack().isHasVelocity()) {
// And then send it again!
player.getSetbackTeleportUtil().executeForceResync();
}
@@ -567,7 +566,9 @@ public class MovementCheckRunner extends Check implements PositionCheck {
// Patch sprint jumping with elytra exploit
if (player.platformPlayer != null && player.isGliding && player.predictedVelocity.isJump() && player.isSprinting && !allowSprintJumpingWithElytra) {
SetbackTeleportUtil.SetbackPosWithVector lastKnownGoodPosition = player.getSetbackTeleportUtil().lastKnownGoodPosition;
lastKnownGoodPosition.setVector(lastKnownGoodPosition.getVector().multiply(new Vector3dm(0.6 * 0.91, 1, 0.6 * 0.91)));
lastKnownGoodPosition.setVectorX(lastKnownGoodPosition.getVectorX() * 0.6 * 0.91);
lastKnownGoodPosition.setVectorZ(lastKnownGoodPosition.getVectorZ() * 0.6 * 0.91);
player.getSetbackTeleportUtil().executeNonSimulatingSetback();
}
@@ -268,22 +268,21 @@ public class PointThreeEstimator {
isNearFluid = false;
// Check for flowing water
Collisions.hasMaterial(player, pointThreeBox, (pair) -> {
final WrappedBlockState state = pair.first();
Collisions.hasMaterial(player, pointThreeBox, (state, x, y, z) -> {
final StateType stateType = state.getType();
if (player.tagManager.block(SyncedTags.CLIMBABLE).contains(stateType) || (stateType == StateTypes.POWDER_SNOW && !player.inVehicle() && player.inventory.getBoots().getType() == ItemTypes.LEATHER_BOOTS)) {
isNearClimbable = true;
}
if (BlockTags.TRAPDOORS.contains(stateType)) {
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, pair.second().getX(), pair.second().getY(), pair.second().getZ(), state);
isNearClimbable = isNearClimbable || Collisions.trapdoorUsableAsLadder(player, x, y, z, state);
}
if (stateType == StateTypes.BUBBLE_COLUMN && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
isNearBubbleColumn = true;
}
if (Materials.isWater(player.getClientVersion(), pair.first()) || pair.first().getType() == StateTypes.LAVA) {
if (Materials.isWater(player.getClientVersion(), state) || state.getType() == StateTypes.LAVA) {
isNearFluid = true;
}
@@ -365,7 +364,7 @@ public class PointThreeEstimator {
// If this tick is the tick after y velocity was by 0, a stepping movement is POSSIBLE to have been hidden
// A bit hacky... is there a better way? I'm unsure...
boolean likelyStepSkip = player.isPointThree() && (data.vector.getY() > -0.08 && data.vector.getY() < 0.06) && couldStep;
boolean likelyStepSkip = player.isPointThree() && (data.vectorY > -0.08 && data.vectorY < 0.06) && couldStep;
// We need to do hypot calculations for all 3 axis
// sqrt(sqrt(x^2 + z^2)^2 + y^2) = hypot(x, z, y)
@@ -411,7 +410,7 @@ public class PointThreeEstimator {
wasAlwaysCertain = false;
// Head hitters return the vector to 0, and then apply gravity to it.
// Not much room for abuse for this, so keep it lenient
return -Math.max(0, vector.vector.getY()) - 0.1 - fluidAddition;
return -Math.max(0, vector.vectorY) - 0.1 - fluidAddition;
} else if (player.uncertaintyHandler.wasAffectedByStuckSpeed()) {
wasAlwaysCertain = false;
// This shouldn't be needed but stuck speed can desync very easily with 0.03...
@@ -425,7 +424,7 @@ public class PointThreeEstimator {
double minMovement = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? 0.003 : 0.005;
// This should likely be refactored, but it works well.
double yVel = vector.vector.getY();
double yVel = vector.vectorY;
double maxYTraveled = 0;
boolean first = true;
do {
@@ -452,7 +451,7 @@ public class PointThreeEstimator {
// We aren't making progress, avoid infinite loop (This can be due to the player not having gravity)
if (yVel == 0) break;
} while (Math.abs(maxYTraveled + vector.vector.getY()) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
} while (Math.abs(maxYTraveled + vector.vectorY) < player.getMovementThreshold()); // Account for uncertainty, don't stop until we simulate past uncertainty point
if (maxYTraveled != 0) {
wasAlwaysCertain = false;
@@ -66,13 +66,13 @@ public class SneakingEstimator extends Check implements PostPredictionCheck {
if (data.isJump() == player.predictedVelocity.isJump() && data.isKnockback() == player.predictedVelocity.isKnockback() && data.isExplosion() == player.predictedVelocity.isExplosion()) {
// Fuck, we are compounding this which is very dangerous. After light testing seems fine... can we do better?
if (player.uncertaintyHandler.lastStuckWest.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckNorth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vector.getX());
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vector.getZ());
sneakingPotentialHiddenVelocity.minX = Math.min(sneakingPotentialHiddenVelocity.minX, data.vectorX);
sneakingPotentialHiddenVelocity.minZ = Math.min(sneakingPotentialHiddenVelocity.minZ, data.vectorZ);
}
if (player.uncertaintyHandler.lastStuckEast.hasOccurredSince(0) || player.uncertaintyHandler.lastStuckSouth.hasOccurredSince(0)) {
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vector.getX());
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vector.getZ());
sneakingPotentialHiddenVelocity.maxX = Math.max(sneakingPotentialHiddenVelocity.maxX, data.vectorX);
sneakingPotentialHiddenVelocity.maxZ = Math.max(sneakingPotentialHiddenVelocity.maxZ, data.vectorZ);
}
}
}
@@ -157,12 +157,12 @@ public class UncertaintyHandler {
Vector3dm maxLocation = new Vector3dm(entityBox.maxX, entityBox.maxY, entityBox.maxZ);
Vector3dm minLocation = new Vector3dm(entityBox.minX, entityBox.minY, entityBox.minZ);
Vector3dm diff = minLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
Vector3dm diff = minLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
fishingRodPullBox.minX = Math.min(0, diff.getX());
fishingRodPullBox.minY = Math.min(0, diff.getY());
fishingRodPullBox.minZ = Math.min(0, diff.getZ());
diff = maxLocation.subtract(new Vector3dm(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ)).multiply(0.1);
diff = maxLocation.subtract(player.lastX, player.lastY + 0.8 * 1.8, player.lastZ).multiply(0.1);
fishingRodPullBox.maxX = Math.max(0, diff.getX());
fishingRodPullBox.maxY = Math.max(0, diff.getY());
fishingRodPullBox.maxZ = Math.max(0, diff.getZ());
@@ -264,7 +264,7 @@ public class UncertaintyHandler {
return 0.06;
// Not worth my time to fix this because checking flying generally sucks - if player was flying in last 2 ticks
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vector.getY()) < (4.5 * player.flySpeed - 0.25))
if ((lastFlyingTicks.hasOccurredSince(5)) && Math.abs(data.vectorY) < (4.5 * player.flySpeed - 0.25))
return 0.06;
double pointThree = player.getMovementThreshold();
@@ -89,12 +89,12 @@ public class MovementTicker {
player.uncertaintyHandler.collidingEntities.add(possibleCollidingEntities);
}
private boolean isHorizontalCollisionSoft(Vector3dm collide) {
double horizontalLengthSquared = collide.getX() * collide.getX() + collide.getZ() * collide.getZ();
private boolean isHorizontalCollisionSoft(double x, double y, double z) {
double horizontalLengthSquared = x * x + z * z;
if (horizontalLengthSquared < 1E-5F) return false;
float xxa = (float) player.predictedVelocity.input.getX();
float zza = (float) player.predictedVelocity.input.getZ();
float xxa = (float) player.predictedVelocity.input.x();
float zza = (float) player.predictedVelocity.input.z();
float yawInRadians = player.yaw * (float) (Math.PI / 180.0);
double sin = player.trigHandler.sin(yawInRadians);
@@ -102,17 +102,17 @@ public class MovementTicker {
double g = xxa * cos - zza * sin;
double h = zza * cos + xxa * sin;
double i = g * g + h * h;
return i >= 1E-5F && Math.acos((g * collide.getX() + h * collide.getZ()) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
return i >= 1E-5F && Math.acos((g * x + h * z) / Math.sqrt(i * horizontalLengthSquared)) < 0.13962634F;
}
public void move(Vector3dm inputVel, Vector3dm collide) {
public void move(Vector3dm inputVel, double x, double y, double z) {
if (player.stuckSpeedMultiplier.getX() < 0.99) {
player.clientVelocity = new Vector3dm();
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2)) {
boolean xAxis = !GrimMath.equal(inputVel.getX(), collide.getX());
boolean zAxis = !GrimMath.equal(inputVel.getZ(), collide.getZ());
boolean xAxis = !GrimMath.equal(inputVel.getX(), x);
boolean zAxis = !GrimMath.equal(inputVel.getZ(), z);
if (xAxis) {
player.clientVelocity.setX(0);
@@ -123,20 +123,20 @@ public class MovementTicker {
}
player.horizontalCollision = xAxis || zAxis;
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(collide);
player.softHorizontalCollision = player.horizontalCollision && isHorizontalCollisionSoft(x, y, z);
} else {
if (inputVel.getX() != collide.getX()) {
if (inputVel.getX() != x) {
player.clientVelocity.setX(0);
}
if (inputVel.getZ() != collide.getZ()) {
if (inputVel.getZ() != z) {
player.clientVelocity.setZ(0);
}
player.horizontalCollision = inputVel.getX() != collide.getX() || inputVel.getZ() != collide.getZ();
player.horizontalCollision = inputVel.getX() != x || inputVel.getZ() != z;
}
player.verticalCollision = inputVel.getY() != collide.getY();
player.verticalCollision = inputVel.getY() != y;
// Avoid order of collisions being wrong because 0.03 movements
// Stepping movement USUALLY means the vehicle in on the ground as vehicles can't jump
@@ -145,7 +145,7 @@ public class MovementTicker {
boolean calculatedOnGround = (player.verticalCollision && inputVel.getY() < 0.0D);
// If the player is on the ground with a y velocity of 0, let the player decide (too close to call)
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() > -SimpleCollisionBox.COLLISION_EPSILON && collide.getY() <= 0 && !player.inVehicle())
if (inputVel.getY() == -SimpleCollisionBox.COLLISION_EPSILON && y > -SimpleCollisionBox.COLLISION_EPSILON && y <= 0 && !player.inVehicle())
calculatedOnGround = player.onGround;
player.clientClaimsLastOnGround = player.onGround;
@@ -177,9 +177,9 @@ public class MovementTicker {
if (player.onGround) {
player.fallDistance = 0;
} else if (collide.getY() < 0) {
player.fallDistance = (player.fallDistance) - collide.getY();
player.vehicleData.lastYd = collide.getY();
} else if (y < 0) {
player.fallDistance = (player.fallDistance) - y;
player.vehicleData.lastYd = y;
}
// Striders call the method for inside blocks AGAIN!
@@ -187,11 +187,11 @@ public class MovementTicker {
Collisions.handleInsideBlocks(player);
}
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(collide.getX(), collide.getY(), collide.getZ()), player.boundingBox, player.onGround);
player.mainSupportingBlockData = MainSupportingBlockPosFinder.findMainSupportingBlockPos(player, player.mainSupportingBlockData, new Vector3d(x, y, z), player.boundingBox, player.onGround);
StateType onBlock = BlockProperties.getOnPos(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
// Hack with 1.14+ poses issue
if (inputVel.getY() != collide.getY()) {
if (inputVel.getY() != y) {
// If the client supports slime blocks
// And the block is a slime block
// Or the block is honey and was replaced by viaversion
@@ -214,14 +214,15 @@ public class MovementTicker {
player.clientVelocity.setY(0);
}
}
collide = PredictionEngine.clampMovementToHardBorder(player, collide);
// TODO reimplement? the comment about reimplementing clampMovementToHardBorder is 3 years old...
// collide = PredictionEngine.clampMovementToHardBorder(player, collide);
// The game disregards movements smaller than 1e-7 (such as in boats)
if (collide.lengthSquared() <= 1e-7
double collideLengthSquared = GrimMath.lengthSquared(x, y, z);
if (collideLengthSquared <= 1e-7
// New condition added in 1.21.2
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collide.lengthSquared() >= 1e-7)) {
collide = new Vector3dm();
&& (player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) || inputVel.lengthSquared() - collideLengthSquared >= 1e-7)) {
x = y = z = 0;
} else if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
Vector3d from = new Vector3d(player.lastX, player.lastY, player.lastZ);
Vector3d to = new Vector3d(player.x, player.y, player.z);
@@ -230,10 +231,10 @@ public class MovementTicker {
}
// This is where vanilla moves the bounding box and sets it
player.predictedVelocity = new VectorData(collide.clone(), player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
player.predictedVelocity = new VectorData(x, y, z, player.predictedVelocity.lastVector, player.predictedVelocity.vectorType);
float f = BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.x, player.y, player.z));
player.clientVelocity.multiply(new Vector3dm(f, 1, f));
player.clientVelocity.multiply(f, 1, f);
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_2)) {
return;
@@ -306,10 +307,10 @@ public class MovementTicker {
// Meaning we should scan upwards!
oldBB.expand(-SimpleCollisionBox.COLLISION_EPSILON);
double posX = Math.max(0, player.predictedVelocity.vector.getX()) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vector.getX()) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vector.getZ()) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vector.getZ()) - SimpleCollisionBox.COLLISION_EPSILON;
double posX = Math.max(0, player.predictedVelocity.vectorX) + SimpleCollisionBox.COLLISION_EPSILON;
double negX = Math.min(0, player.predictedVelocity.vectorX) - SimpleCollisionBox.COLLISION_EPSILON;
double posZ = Math.max(0, player.predictedVelocity.vectorZ) + SimpleCollisionBox.COLLISION_EPSILON;
double negZ = Math.min(0, player.predictedVelocity.vectorZ) - SimpleCollisionBox.COLLISION_EPSILON;
boolean xAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(negX, 0, 0).expandMax(posX, 0, 0));
boolean zAxisCollision = !Collisions.isEmpty(player, oldBB.expandMin(0, 0, negZ).expandMax(0, 0, posZ));
@@ -435,7 +436,7 @@ public class MovementTicker {
// 1.13 and below players can't climb ladders while touching water
// yes, 1.13 players cannot climb ladders underwater
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) && player.isClimbing) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
}
} else {
@@ -446,14 +447,14 @@ public class MovementTicker {
// Lava movement changed in 1.16
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16) && player.slightlyTouchingLava) {
player.clientVelocity = player.clientVelocity.multiply(new Vector3dm(0.5D, 0.800000011920929D, 0.5D));
player.clientVelocity = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity);
player.clientVelocity = player.clientVelocity.multiply(0.5D, 0.800000011920929D, 0.5D);
player.clientVelocity.setY(FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, player.clientVelocity.getY()));
} else {
player.clientVelocity.multiply(0.5D);
}
if (player.hasGravity)
player.clientVelocity.add(new Vector3dm(0.0D, -playerGravity / 4.0D, 0.0D));
player.clientVelocity.add(0.0D, -playerGravity / 4.0D, 0.0D);
} else if (player.isGliding) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5) && Collisions.onClimbable(player, player.lastX, player.lastY, player.lastZ)) {
@@ -30,7 +30,7 @@ public class MovementTickerStrider extends MovementTickerRideable {
if (isAbove(player) && player.compensatedWorld.getLavaFluidLevelAt((int) Math.floor(player.x), (int) Math.floor(player.y + 1), (int) Math.floor(player.z)) == 0) {
player.onGround = true;
} else {
player.clientVelocity.multiply(0.5).add(new Vector3dm(0, 0.05, 0));
player.clientVelocity.multiply(0.5).add(0, 0.05, 0);
}
}
}
@@ -9,6 +9,7 @@ import ac.grim.grimac.utils.data.KnownInput;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.math.VectorUtils;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -31,22 +32,22 @@ public class PredictionEngine {
return outputVel;
}
public static Vector3dm transformInputsToVector(GrimPlayer player, Vector3dm theoreticalInput) {
public static Vec2d transformInputsToVector(GrimPlayer player, int strafe, int forward) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) {
Vec2 moveVector = new Vec2((float) theoreticalInput.getX(), (float) theoreticalInput.getZ()).normalized();
Vec2 moveVector = new Vec2((float) strafe, (float) forward).normalized();
Vec2 input = modifyInput(player, moveVector);
return new Vector3dm(input.x(), 0, input.y());
return new Vec2d(input.x(), input.y());
}
float bestPossibleX;
float bestPossibleZ;
// Slow movement was determined by the previous pose
if (player.isSlowMovement) {
bestPossibleX = (float) (theoreticalInput.getX() * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (theoreticalInput.getZ() * player.sneakingSpeedMultiplier);
bestPossibleX = (float) (strafe * player.sneakingSpeedMultiplier);
bestPossibleZ = (float) (forward * player.sneakingSpeedMultiplier);
} else {
bestPossibleX = Math.min(Math.max(-1f, Math.round(theoreticalInput.getX())), 1f);
bestPossibleZ = Math.min(Math.max(-1f, Math.round(theoreticalInput.getZ())), 1f);
bestPossibleX = Math.min(Math.max(-1f, strafe), 1f);
bestPossibleZ = Math.min(Math.max(-1f, forward), 1f);
}
if (player.packetStateData.isSlowedByUsingItem()) {
@@ -54,18 +55,23 @@ public class PredictionEngine {
bestPossibleZ *= 0.2F;
}
Vector3dm inputVector = new Vector3dm(bestPossibleX, 0, bestPossibleZ);
inputVector.multiply(0.98F);
double inputVectorX = bestPossibleX;
double inputVectorZ = bestPossibleZ;
inputVectorX *= 0.98F;
inputVectorZ *= 0.98F;
// Simulate float rounding imprecision
inputVector = new Vector3dm((float) inputVector.getX(), (float) inputVector.getY(), (float) inputVector.getZ());
inputVectorX = (float) inputVectorX;
inputVectorZ = (float) inputVectorZ;
if (inputVector.lengthSquared() > 1) {
double d0 = Math.sqrt(inputVector.getX() * inputVector.getX() + inputVector.getY() * inputVector.getY() + inputVector.getZ() * inputVector.getZ());
inputVector = new Vector3dm(inputVector.getX() / d0, inputVector.getY() / d0, inputVector.getZ() / d0);
double lengthSquared = GrimMath.lengthSquared(inputVectorX, inputVectorZ);
if (lengthSquared > 1) {
double d0 = Math.sqrt(lengthSquared);
inputVectorX = inputVectorX / d0;
inputVectorZ = inputVectorZ / d0;
}
return inputVector;
return new Vec2d(inputVectorX, inputVectorZ);
}
public static Vec2 modifyInput(GrimPlayer player, Vec2 moveVector) {
@@ -140,7 +146,7 @@ public class PredictionEngine {
// Client velocity - before collision and carried into the next tick
// Predicted velocity - after collision and not carried into the next tick
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vector);
new MovementTickerPlayer(player).move(player.clientVelocity.clone(), player.predictedVelocity.vectorX, player.predictedVelocity.vectorY, player.predictedVelocity.vectorZ);
endOfTick(player, player.gravity);
}
@@ -234,7 +240,7 @@ public class PredictionEngine {
beforeCollisionMovement = primaryPushMovement;
// We basically want to avoid falsing ground spoof, try to find a vector that works
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vector.getY()))
if (player.wouldCollisionResultFlagGroundSpoof(primaryPushMovement.getY(), bestCollisionVel.vectorY))
resultAccuracy += 0.0001 * 0.0001;
bestInput = resultAccuracy;
@@ -259,7 +265,7 @@ public class PredictionEngine {
}
private Pair<Vector3dm, Vector3dm> doSeekingWallCollisions(GrimPlayer player, Vector3dm primaryPushMovement, Vector3dm originalClientVel, VectorData clientVelAfterInput) {
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vector.getY() == 0;
boolean vehicleKB = player.inVehicle() && clientVelAfterInput.isKnockback() && clientVelAfterInput.vectorY == 0;
// Extra collision epsilon required for vehicles to be accurate
double xAdditional = Math.signum(primaryPushMovement.getX()) * SimpleCollisionBox.COLLISION_EPSILON;
// The server likes sending y=0 kb "lifting" the player off the ground.
@@ -329,10 +335,9 @@ public class PredictionEngine {
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
// Calculate the Y velocity after friction
Vector3dm hackyClimbVector = new Vector3dm(0, 0.2, 0);
PredictionEngineNormal.staticVectorEndOfTick(player, hackyClimbVector);
double y = PredictionEngineNormal.staticVectorEndOfTickY(player, 0.2);
pointThreePossibilities.add(new VectorData(hackyClimbVector, VectorData.VectorType.ZeroPointZeroThree));
pointThreePossibilities.add(new VectorData(0, y, 0, VectorData.VectorType.ZeroPointZeroThree));
}
// This is a secure method to add jumping vectors to this list
@@ -357,19 +362,24 @@ public class PredictionEngine {
for (VectorData vectorData : data) {
// Sneaking in water
if (vectorData.isKnockback() && player.baseTickAddition.lengthSquared() != 0) {
vectorData.vector = vectorData.vector.add(player.baseTickAddition);
vectorData.vectorX += player.baseTickAddition.getX();
vectorData.vectorY += player.baseTickAddition.getY();
vectorData.vectorZ += player.baseTickAddition.getZ();
}
// Water pushing movement is affected by initial velocity due to 0.003 eating pushing in the past
if (vectorData.isKnockback() && player.baseTickWaterPushing.lengthSquared() != 0) {
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13)) {
Vector3dm vec3 = player.baseTickWaterPushing.clone();
if (Math.abs(vectorData.vector.getX()) < 0.003 && Math.abs(vectorData.vector.getZ()) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
if (Math.abs(vectorData.vectorX) < 0.003 && Math.abs(vectorData.vectorZ) < 0.003 && player.baseTickWaterPushing.length() < 0.0045000000000000005D) {
vec3 = vec3.normalize().multiply(0.0045000000000000005);
}
vectorData.vector = vectorData.vector.add(vec3);
vectorData.vectorX += vec3.getX();
vectorData.vectorY += vec3.getY();
vectorData.vectorZ += vec3.getZ();
} else {
vectorData.vector = vectorData.vector.add(player.baseTickWaterPushing);
vectorData.vectorX += player.baseTickWaterPushing.getX();
vectorData.vectorY += player.baseTickWaterPushing.getY();
vectorData.vectorZ += player.baseTickWaterPushing.getZ();
}
}
}
@@ -405,7 +415,9 @@ public class PredictionEngine {
if (!player.inVehicle() || player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_21_5)) return;
for (VectorData vectorData : data) {
vectorData.vector = vectorData.vector.clone().multiply(0.98);
vectorData.vectorX = vectorData.vectorX * 0.98;
vectorData.vectorY = vectorData.vectorY * 0.98;
vectorData.vectorZ = vectorData.vectorZ * 0.98;
}
}
@@ -413,11 +425,11 @@ public class PredictionEngine {
for (int x = 1; x <= Math.min(player.maxAttackSlow, 5); x++) {
for (VectorData data : new HashSet<>(velocities)) {
if (player.minAttackSlow > 0) {
data.vector.setX(data.vector.getX() * 0.6);
data.vector.setZ(data.vector.getZ() * 0.6);
data.vectorX = data.vectorX * 0.6;
data.vectorZ = data.vectorZ * 0.6;
data.addVectorType(VectorData.VectorType.AttackSlow);
} else {
velocities.add(data.returnNewModified(data.vector.clone().multiply(new Vector3dm(0.6, 1, 0.6)), VectorData.VectorType.AttackSlow));
velocities.add(data.returnNewModified(data.vectorX * 0.6, data.vectorY, data.vectorZ * 0.6, VectorData.VectorType.AttackSlow));
}
}
@@ -442,45 +454,56 @@ public class PredictionEngine {
Set<VectorData> vectors = stupidVectors && stuckOnEdge ? new HashSet<>(velocities) : velocities;
for (VectorData vector : vectors) {
if (stupidVectors) {
if (Collisions.getHorizontalDistanceSqr(vector.vector) < 9.0E-6) {
if (Collisions.getHorizontalDistanceSqr(vector.vectorX, vector.vectorZ) < 9.0E-6) {
if (stuckOnEdge) {
VectorData edgeVector = vector.returnNewModified(vector.vector.clone(), vector.vectorType);
if (Math.abs(edgeVector.vector.getY()) < minimumMovement) {
edgeVector.vector.setY(0D);
VectorData edgeVector = vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, vector.vectorType);
if (Math.abs(edgeVector.vectorY) < minimumMovement) {
edgeVector.setY(0D);
}
velocities.add(edgeVector);
}
vector.vector.setX(0D);
vector.vector.setZ(0D);
vector.setX(0D);
vector.setZ(0D);
}
} else {
if (Math.abs(vector.vector.getX()) < minimumMovement) {
vector.vector.setX(0D);
if (Math.abs(vector.vectorX) < minimumMovement) {
vector.setX(0D);
}
if (Math.abs(vector.vector.getZ()) < minimumMovement) {
vector.vector.setZ(0D);
if (Math.abs(vector.vectorZ) < minimumMovement) {
vector.setZ(0D);
}
}
if (Math.abs(vector.vector.getY()) < minimumMovement) {
vector.vector.setY(0D);
if (Math.abs(vector.vectorY) < minimumMovement) {
vector.setY(0D);
}
}
}
public void addExplosionToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
if (player.likelyExplosions == null && player.firstBreadExplosion == null) return;
for (VectorData vector : new HashSet<>(existingVelocities)) {
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
if (player.likelyExplosions != null) {
existingVelocities.add(new VectorData(vector.vector.clone().add(player.likelyExplosions.vector), vector, VectorData.VectorType.Explosion));
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
existingVelocities.add(new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion));
}
if (player.firstBreadExplosion != null) {
existingVelocities.add(new VectorData(vector.vector.clone().add(player.firstBreadExplosion.vector), vector, VectorData.VectorType.Explosion)
.returnNewModified(vector.vector.clone().add(player.firstBreadExplosion.vector), VectorData.VectorType.FirstBreadExplosion));
// Previously we called Vector3dm.add(player.likelyExplosions.vector) after checking (player.firstBreadExplosion != null)
// which would've thrown an NPE if player.likelyExplosions could be null here, we can ignore this
// empirically this means when firstBreadExplosion != null then likelyExplosions is also != null
final double newX = player.likelyExplosions.vector.getX() + vector.vectorX;
final double newY = player.likelyExplosions.vector.getY() + vector.vectorY;
final double newZ = player.likelyExplosions.vector.getZ() + vector.vectorZ;
VectorData intermediateData = new VectorData(newX, newY, newZ, vector, VectorData.VectorType.Explosion);
VectorData finalData = new VectorData(newX, newY, newZ, intermediateData, VectorData.VectorType.FirstBreadExplosion);
existingVelocities.add(finalData);
}
}
}
@@ -533,16 +556,18 @@ public class PredictionEngine {
bScore -= 1;
// If the player is on the ground but the vector leads the player off the ground
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vector.getY() >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && a.vectorY >= 0)
aScore += 2;
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vector.getY() >= 0)
if ((player.inVehicle() ? player.clientControlledVerticalCollision : player.onGround) && b.vectorY >= 0)
bScore += 2;
if (aScore != bScore)
return Integer.compare(aScore, bScore);
return Double.compare(a.vector.distanceSquared(player.actualMovement), b.vector.distanceSquared(player.actualMovement));
return Double.compare(
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), a.vectorX, a.vectorY, a.vectorZ),
GrimMath.distanceSquared(player.actualMovement.getX(), player.actualMovement.getY(), player.actualMovement.getZ(), b.vectorX, b.vectorY, b.vectorZ));
}
public Vector3dm handleStartingVelocityUncertainty(GrimPlayer player, VectorData vector, Vector3dm targetVec) {
@@ -596,69 +621,81 @@ public class PredictionEngine {
// 0.075 seems safe?
//
// Be somewhat careful as there is an antikb (for horizontal) that relies on this lenience
Vector3dm uncertainty = new Vector3dm(avgColliding * 0.08, additionVertical, avgColliding * 0.08);
final double uncertaintyX = avgColliding * 0.08;
final double uncertaintyY = additionVertical;
final double uncertaintyZ = avgColliding * 0.08;
Vector3dm min = new Vector3dm(player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal, -bonusY + player.uncertaintyHandler.yNegativeUncertainty, player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal);
Vector3dm max = new Vector3dm(player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal, bonusY + player.uncertaintyHandler.yPositiveUncertainty, player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal);
final double minUncertaintyX = player.uncertaintyHandler.xNegativeUncertainty - additionHorizontal;
final double minUncertaintyY = player.uncertaintyHandler.yNegativeUncertainty - bonusY;
final double minUncertaintyZ = player.uncertaintyHandler.zNegativeUncertainty - additionHorizontal;
Vector3dm minVector = vector.vector.clone().add(min.subtract(uncertainty));
Vector3dm maxVector = vector.vector.clone().add(max.add(uncertainty));
final double maxUncertaintyX = player.uncertaintyHandler.xPositiveUncertainty + additionHorizontal;
final double maxUncertaintyY = player.uncertaintyHandler.yPositiveUncertainty + bonusY;
final double maxUncertaintyZ = player.uncertaintyHandler.zPositiveUncertainty + additionHorizontal;
double minVecX = vector.vectorX + (minUncertaintyX - uncertaintyX);
double minVecY = vector.vectorY + (minUncertaintyY - uncertaintyY);
double minVecZ = vector.vectorZ + (minUncertaintyZ - uncertaintyZ);
double maxVecX = vector.vectorX + (maxUncertaintyX + uncertaintyX);
double maxVecY = vector.vectorY + (maxUncertaintyY + uncertaintyY);
double maxVecZ = vector.vectorZ + (maxUncertaintyZ + uncertaintyZ);
// Handle the player landing within 0.03 movement, which resets Y velocity
if (player.uncertaintyHandler.onGroundUncertain && vector.vector.getY() < 0) {
maxVector.setY(0);
if (player.uncertaintyHandler.onGroundUncertain && vector.vectorY < 0) {
maxVecY = 0;
}
// Handles stuff like missing idle packet causing gravity to be missed (plus 0.03 of course)
double gravityOffset = player.pointThreeEstimator.getAdditionalVerticalUncertainty(vector);
if (gravityOffset > 0) {
maxVector.setY(maxVector.getY() + gravityOffset);
maxVecY += gravityOffset;
} else {
minVector.setY(minVector.getY() + gravityOffset);
minVecY += gravityOffset;
}
// Handle vertical fluid pushing within 0.03
double verticalFluid = player.pointThreeEstimator.getVerticalFluidPushingUncertainty(vector);
minVector.setY(minVector.getY() - verticalFluid);
minVecY -= verticalFluid;
// Handle vertical bubble column stupidity within 0.03
double bubbleFluid = player.pointThreeEstimator.getVerticalBubbleUncertainty(vector);
maxVector.setY(maxVector.getY() + bubbleFluid);
minVector.setY(minVector.getY() - bubbleFluid);
maxVecY += bubbleFluid;
minVecY -= bubbleFluid;
// We can't simulate the player's Y velocity, unknown number of ticks with a gravity change
// Feel free to simulate all 104857600000000000000000000 possibilities!
if (!player.pointThreeEstimator.canPredictNextVerticalMovement()) {
minVector.setY(minVector.getY() - player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY));
minVecY -= player.compensatedEntities.self.getAttributeValue(Attributes.GRAVITY);
}
// Hidden slime block bounces by missing idle tick and 0.03
if (player.actualMovement.getY() >= 0 && player.uncertaintyHandler.influencedByBouncyBlock()) {
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty != 0 && !vector.isJump()) { // jumping overrides slime block
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVector.getY()) {
maxVector.setY(player.uncertaintyHandler.thisTickSlimeBlockUncertainty);
if (player.uncertaintyHandler.thisTickSlimeBlockUncertainty > maxVecY) {
maxVecY = player.uncertaintyHandler.thisTickSlimeBlockUncertainty;
}
if (minVector.getY() > 0) minVector.setY(0);
if (minVecY > 0) minVecY = 0;
}
}
if (vector.isZeroPointZeroThree() && vector.isSwimHop()) {
minVector.setY(minVector.getY() - 0.06); // Fluid pushing downwards hidden by 0.03
minVecY -= 0.06; // Fluid pushing downwards hidden by 0.03
}
SimpleCollisionBox box = new SimpleCollisionBox(minVector, maxVector);
SimpleCollisionBox box = new SimpleCollisionBox(minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
box.sort();
// https://github.com/MWHunter/Grim/issues/398
// Thank mojang for removing the idle packet resulting in this hacky mess
double levitation = player.pointThreeEstimator.positiveLevitation(maxVector.getY());
double levitation = player.pointThreeEstimator.positiveLevitation(maxVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.positiveLevitation(minVector.getY());
levitation = player.pointThreeEstimator.positiveLevitation(minVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(maxVector.getY());
levitation = player.pointThreeEstimator.negativeLevitation(maxVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
levitation = player.pointThreeEstimator.negativeLevitation(minVector.getY());
levitation = player.pointThreeEstimator.negativeLevitation(minVecY);
box.combineToMinimum(box.minX, levitation, box.minZ);
@@ -669,12 +706,12 @@ public class PredictionEngine {
box.maxZ += sneaking.getSneakingPotentialHiddenVelocity().maxZ;
if (player.uncertaintyHandler.fireworksBox != null) {
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vector.getX());
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vector.getY());
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vector.getZ());
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vector.getX());
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vector.getY());
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vector.getZ());
double minXdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minX - originalVec.vectorX);
double minYdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minY - originalVec.vectorY);
double minZdiff = Math.min(0, player.uncertaintyHandler.fireworksBox.minZ - originalVec.vectorZ);
double maxXdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxX - originalVec.vectorX);
double maxYdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxY - originalVec.vectorY);
double maxZdiff = Math.max(0, player.uncertaintyHandler.fireworksBox.maxZ - originalVec.vectorZ);
box.expandMin(minXdiff, minYdiff, minZdiff);
box.expandMax(maxXdiff, maxYdiff, maxZdiff);
@@ -727,7 +764,7 @@ public class PredictionEngine {
//
// Or the player is switching in and out of controlling a vehicle, in which friction messes it up
//
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vector.getY() > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vector.getY() + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
if (player.uncertaintyHandler.lastVehicleSwitch.hasOccurredSince(0) || player.uncertaintyHandler.lastHardCollidingLerpingEntity.hasOccurredSince(3) || (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_13) && vector.vectorY > 0 && vector.isZeroPointZeroThree() && !Collisions.isEmpty(player, GetBoundingBox.getBoundingBoxFromPosAndSize(player, player.lastX, vector.vectorY + player.lastY + 0.6, player.lastZ, 0.6f, 1.26f)))) {
box.expandToAbsoluteCoordinates(0, 0, 0);
}
@@ -753,22 +790,26 @@ public class PredictionEngine {
box.expand(0.001); // Ignore 1e-3 offsets as we don't know starting vel
}
minVector = box.min();
maxVector = box.max();
minVecX = box.minX;
minVecY = box.minY;
minVecZ = box.minZ;
maxVecX = box.maxX;
maxVecY = box.maxY;
maxVecZ = box.maxZ;
if (pistonX != 0) {
minVector.setX(Math.min(minVector.getX() - pistonX, pistonX));
maxVector.setX(Math.max(maxVector.getX() + pistonX, pistonX));
minVecX = Math.min(minVecX - pistonX, pistonX);
maxVecX = Math.max(maxVecX + pistonX, pistonX);
}
if (pistonY != 0) {
minVector.setY(Math.min(minVector.getY() - pistonY, pistonY));
maxVector.setY(Math.max(maxVector.getY() + pistonY, pistonY));
minVecY = Math.min(minVecY - pistonY, pistonY);
maxVecY = Math.max(maxVecY + pistonY, pistonY);
}
if (pistonZ != 0) {
minVector.setZ(Math.min(minVector.getZ() - pistonZ, pistonZ));
maxVector.setZ(Math.max(maxVector.getZ() + pistonZ, pistonZ));
minVecZ = Math.min(minVecZ - pistonZ, pistonZ);
maxVecZ = Math.max(maxVecZ + pistonZ, pistonZ);
}
return VectorUtils.cutBoxToVector(targetVec, minVector, maxVector);
return VectorUtils.cutBoxToVector(targetVec, minVecX, minVecY, minVecZ, maxVecX, maxVecY, maxVecZ);
}
public void endOfTick(GrimPlayer player, double d) {
@@ -828,15 +869,20 @@ public class PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
Vector3dm input = transformInputsToVector(player, new Vector3dm(strafe, 0, forward));
VectorData result = new VectorData(possibleLastTickOutput.vector.clone()
.add(getMovementResultFromInput(player, input, speed, player.yaw)),
Vec2d input = transformInputsToVector(player, strafe, forward);
Vector3dm movementResultFromInput = getMovementResultFromInput(player, input.x(), 0, input.z(), speed, player.yaw);
VectorData result = new VectorData(possibleLastTickOutput.vectorX + movementResultFromInput.getX(),
possibleLastTickOutput.vectorY + movementResultFromInput.getY(),
possibleLastTickOutput.vectorZ + movementResultFromInput.getZ(),
possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = input;
if (applyStuckSpeed != 0) {
result = result.returnNewModified(result.vector.clone().multiply(player.stuckSpeedMultiplier), VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(result.vectorX * player.stuckSpeedMultiplier.getX(),
result.vectorY * player.stuckSpeedMultiplier.getY(),
result.vectorZ * player.stuckSpeedMultiplier.getZ(),
VectorData.VectorType.StuckMultiplier);
}
result = result.returnNewModified(handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
// Signal that we need to flip sneaking bounding box
if (loopUsingItem == 1)
result = result.returnNewModified(VectorData.VectorType.Flip_Use_Item);
@@ -902,25 +948,23 @@ public class PredictionEngine {
// This is just the vanilla equation, which accepts invalid inputs greater than 1
// We need it because of collision support when a player is using speed
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float f2InRadians = GrimMath.radians(f2);
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float speed, float yaw) {
float f2InRadians = GrimMath.radians(yaw);
float f3 = player.trigHandler.sin(f2InRadians);
float f4 = player.trigHandler.cos(f2InRadians);
double xResult = inputVector.getX() * f4 - inputVector.getZ() * f3;
double zResult = inputVector.getZ() * f4 + inputVector.getX() * f3;
double xResult = x * f4 - z * f3;
double zResult = z * f4 + x * f3;
return new Vector3dm(xResult * f, 0, zResult * f);
return new Vector3dm(xResult * speed, 0, zResult * speed);
}
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
return vector;
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
return new Vector3dm(x, y, z);
}
public void doJump(GrimPlayer player, Vector3dm vector) {
if (!player.lastOnGround || player.onGround)
return;
JumpPower.jumpFromGround(player, vector);
public void doJump(GrimPlayer player, double[] vectorArr) {
if (!player.lastOnGround || player.onGround) return;
JumpPower.jumpFromGround(player, vectorArr);
}
}
@@ -3,6 +3,7 @@ package ac.grim.grimac.predictionengine.predictions;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.ReachUtils;
import com.github.retrooper.packetevents.protocol.attribute.Attributes;
@@ -13,11 +14,13 @@ import java.util.List;
import java.util.Set;
public class PredictionEngineElytra extends PredictionEngine {
public static Vector3dm getElytraMovement(GrimPlayer player, Vector3dm vector, Vector3dm lookVector) {
public static void applyElytraMovementInPlace(GrimPlayer player,
double[] vectorArr,
double lookX, double lookY, double lookZ) {
float pitchRadians = GrimMath.radians(player.pitch);
double horizontalSqrt = Math.sqrt(lookVector.getX() * lookVector.getX() + lookVector.getZ() * lookVector.getZ());
double horizontalLength = vector.clone().setY(0).length();
double length = lookVector.length();
double horizontalSqrt = Math.sqrt(lookX * lookX + lookZ * lookZ);
double horizontalLength = GrimMath.length(vectorArr[0], 0, vectorArr[2]);
double length = GrimMath.length(lookX, lookY, lookZ);
// Mojang changed from using their math to using regular java math in 1.18.2 elytra movement
double vertCosRotation = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_18_2) ? Math.cos(pitchRadians) : player.trigHandler.cos(pitchRadians);
@@ -31,27 +34,30 @@ public class PredictionEngineElytra extends PredictionEngine {
recalculatedGravity = player.getClientVersion().isOlderThan(ClientVersion.V_1_20_5) ? 0.01 : Math.min(recalculatedGravity, 0.01);
}
vector.add(new Vector3dm(0.0D, recalculatedGravity * (-1.0D + vertCosRotation * 0.75D), 0.0D));
vectorArr[1] += recalculatedGravity * (-1.0D + vertCosRotation * 0.75D);
double d5;
// Handle slowing the player down when falling
if (vector.getY() < 0.0D && horizontalSqrt > 0.0D) {
d5 = vector.getY() * -0.1D * vertCosRotation;
vector.add(new Vector3dm(lookVector.getX() * d5 / horizontalSqrt, d5, lookVector.getZ() * d5 / horizontalSqrt));
if (vectorArr[1] < 0.0D && horizontalSqrt > 0.0D) {
d5 = vectorArr[1] * -0.1D * vertCosRotation;
vectorArr[0] += lookX * d5 / horizontalSqrt;
vectorArr[1] += d5;
vectorArr[2] += lookZ * d5 / horizontalSqrt;
}
// Handle accelerating the player when they are looking down
if (pitchRadians < 0.0F && horizontalSqrt > 0.0D) {
d5 = horizontalLength * (double) (-player.trigHandler.sin(pitchRadians)) * 0.04D;
vector.add(new Vector3dm(-lookVector.getX() * d5 / horizontalSqrt, d5 * 3.2D, -lookVector.getZ() * d5 / horizontalSqrt));
vectorArr[0] += -lookX * d5 / horizontalSqrt;
vectorArr[1] += d5 * 3.2D;
vectorArr[2] += -lookZ * d5 / horizontalSqrt;
}
// Handle accelerating the player sideways
if (horizontalSqrt > 0) {
vector.add(new Vector3dm((lookVector.getX() / horizontalSqrt * horizontalLength - vector.getX()) * 0.1D, 0.0D, (lookVector.getZ() / horizontalSqrt * horizontalLength - vector.getZ()) * 0.1D));
vectorArr[0] += (lookX / horizontalSqrt * horizontalLength - vectorArr[0]) * 0.1D;
vectorArr[2] += (lookZ / horizontalSqrt * horizontalLength - vectorArr[2]) * 0.1D;
}
return vector;
}
// Inputs have no effect on movement
@@ -65,11 +71,19 @@ public class PredictionEngineElytra extends PredictionEngine {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
for (VectorData data : possibleVectors) {
Vector3dm elytraResult = getElytraMovement(player, data.vector.clone(), currentLook);
if (applyStuckSpeed != 0) elytraResult.multiply(player.stuckSpeedMultiplier);
elytraResult.multiply(new Vector3dm(0.99F, 0.98F, 0.99F));
VectorData modified = data.returnNewModified(elytraResult, VectorData.VectorType.InputResult);
modified.input = new Vector3dm(0, 0, 0);
double[] vector = {data.vectorX, data.vectorY, data.vectorZ};
applyElytraMovementInPlace(player, vector, currentLook.getX(), currentLook.getY(), currentLook.getZ());
if (applyStuckSpeed != 0) {
vector[0] *= player.stuckSpeedMultiplier.getX();
vector[1] *= player.stuckSpeedMultiplier.getY();
vector[2] *= player.stuckSpeedMultiplier.getZ();
}
vector[0] *= 0.99F;
vector[1] *= 0.98F;
vector[2] *= 0.99f;
VectorData modified = data.returnNewModified(vector[0], vector[1], vector[2], VectorData.VectorType.InputResult);
modified.input = new Vec2d(0, 0);
results.add(modified);
}
}
@@ -11,18 +11,23 @@ import java.util.Set;
public class PredictionEngineLava extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
} else {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(startX, startY + 0.04f, startZ, vector, VectorData.VectorType.Jump));
}
if (player.slightlyTouchingLava && player.lastOnGround && !player.onGround) {
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
}
}
}
@@ -19,20 +19,24 @@ import java.util.Set;
public class PredictionEngineNormal extends PredictionEngine {
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector) {
double adjustedY = vector.getY();
public static double staticVectorEndOfTickY(GrimPlayer player, double y) {
double adjustedY = y;
final OptionalInt levitation = player.compensatedEntities.getPotionLevelForPlayer(PotionTypes.LEVITATION);
if (levitation.isPresent()) {
adjustedY += (0.05 * (levitation.getAsInt() + 1) - vector.getY()) * 0.2;
adjustedY += (0.05 * (levitation.getAsInt() + 1) - y) * 0.2;
// Reset fall distance with levitation
player.fallDistance = 0;
} else if (player.hasGravity) {
adjustedY -= player.gravity;
}
vector.setX(vector.getX() * player.friction);
vector.setY(adjustedY * 0.98F);
vector.setZ(vector.getZ() * player.friction);
return adjustedY * 0.98F;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData) {
vectorData.vectorX = vectorData.vectorX * player.friction;
vectorData.vectorY = staticVectorEndOfTickY(player, vectorData.vectorY);
vectorData.vectorZ = vectorData.vectorZ * player.friction;
}
@Override
@@ -41,8 +45,9 @@ public class PredictionEngineNormal extends PredictionEngine {
return;
}
for (VectorData vector : new HashSet<>(existingVelocities)) {
Vector3dm jump = vector.vector.clone();
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
double[] jumpValues = {vector.vectorX, vector.vectorY, vector.vectorZ};
if (!player.isFlying) {
// Negative jump boost does not allow the player to leave the ground
@@ -54,17 +59,17 @@ public class PredictionEngineNormal extends PredictionEngine {
if (((jumpBoost.isEmpty() || jumpBoost.getAsInt() >= 0) && player.onGround) || !player.lastOnGround)
return;
JumpPower.jumpFromGround(player, jump);
JumpPower.jumpFromGround(player, jumpValues);
} else {
jump.add(new Vector3dm(0, player.flySpeed * 3, 0));
jumpValues[1] = player.flySpeed * 3;
if (!player.wasFlying) {
Vector3dm edgeCaseJump = jump.clone();
JumpPower.jumpFromGround(player, edgeCaseJump);
existingVelocities.add(vector.returnNewModified(edgeCaseJump, VectorData.VectorType.Jump));
double[] edgeCaseJumpValues = {jumpValues[0], jumpValues[1], jumpValues[2]};
JumpPower.jumpFromGround(player, edgeCaseJumpValues);
existingVelocities.add(vector.returnNewModified(edgeCaseJumpValues[0], edgeCaseJumpValues[1], edgeCaseJumpValues[2], VectorData.VectorType.Jump));
}
}
existingVelocities.add(vector.returnNewModified(jump, VectorData.VectorType.Jump));
existingVelocities.add(vector.returnNewModified(jumpValues[0], jumpValues[1], jumpValues[2], VectorData.VectorType.Jump));
}
}
@@ -86,32 +91,30 @@ public class PredictionEngineNormal extends PredictionEngine {
if (player.lastWasClimbing == 0 && (player.pointThreeEstimator.isNearClimbable() || player.isClimbing) && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14)
|| !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5))) || walkingOnPowderSnow) {
Vector3dm ladderVelocity = player.clientVelocity.clone().setY(0.2);
staticVectorEndOfTick(player, ladderVelocity);
player.lastWasClimbing = ladderVelocity.getY();
player.lastWasClimbing = staticVectorEndOfTickY(player, 0.2);
}
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector.vector);
staticVectorEndOfTick(player, vector);
}
}
@Override
public Vector3dm handleOnClimbable(Vector3dm vector, GrimPlayer player) {
public Vector3dm handleOnClimbable(double x, double y, double z, GrimPlayer player) {
if (player.isClimbing) {
// Reset fall distance when climbing
player.fallDistance = 0;
vector.setX(GrimMath.clamp(vector.getX(), -0.15F, 0.15F));
vector.setZ(GrimMath.clamp(vector.getZ(), -0.15F, 0.15F));
vector.setY(Math.max(vector.getY(), -0.15F));
x = GrimMath.clamp(x, -0.15F, 0.15F);
z = GrimMath.clamp(z, -0.15F, 0.15F);
y = Math.max(y, -0.15F);
// Yes, this uses shifting not crouching
if (vector.getY() < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
vector.setY(0.0);
if (y < 0.0 && !(player.compensatedWorld.getBlockType(player.lastX, player.lastY, player.lastZ) == StateTypes.SCAFFOLDING) && player.isSneaking && !player.isFlying) {
y = 0.0;
}
}
return vector;
return new Vector3dm(x, y, z);
}
}
@@ -19,12 +19,19 @@ public class PredictionEngineWater extends PredictionEngine {
private double playerGravity;
private float swimmingFriction;
public static void staticVectorEndOfTick(GrimPlayer player, Vector3dm vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
Vector3dm fluidVector = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, vector);
vector.setX(fluidVector.getX());
vector.setY(fluidVector.getY());
vector.setZ(fluidVector.getZ());
public static void staticVectorEndOfTick(GrimPlayer player, double[] vector, float swimmingFriction, double playerGravity, boolean isFalling) {
vector[0] *= swimmingFriction;
vector[1] *= 0.8F;
vector[2] *= swimmingFriction;
double y = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vector[1]);
vector[1] = y;
}
public static void staticVectorEndOfTick(GrimPlayer player, VectorData vectorData, float swimmingFriction, double playerGravity, boolean isFalling) {
vectorData.vectorX *= swimmingFriction;
vectorData.vectorY *= 0.8F;
vectorData.vectorZ *= swimmingFriction;
vectorData.vectorY = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, vectorData.vectorY);
}
public static Set<VectorData> transformSwimmingVectors(GrimPlayer player, Set<VectorData> base) {
@@ -45,7 +52,7 @@ public class PredictionEngineWater extends PredictionEngine {
double scalar = lookYAmount < -0.2 ? 0.085 : 0.06;
// The player can always press jump and activate this
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vector.getX(), vector.vector.getY() + ((lookYAmount - vector.vector.getY()) * scalar), vector.vector.getZ()), VectorData.VectorType.SwimmingSpace));
swimmingVelocities.add(vector.returnNewModified(new Vector3dm(vector.vectorX, vector.vectorY + ((lookYAmount - vector.vectorY) * scalar), vector.vectorZ), VectorData.VectorType.SwimmingSpace));
// This scenario will occur if the player does not press jump and the other conditions are met
// Theoretically we should check this BEFORE allowing no look, but there isn't a cheat that takes advantage of this yet
@@ -54,7 +61,7 @@ public class PredictionEngineWater extends PredictionEngine {
// If you feel adventurous, re-add the following line to eliminate this unfair disadvantage
//if (d > 0.0 && player.world.getFluidLevelAt(player.lastX, player.lastY + 1.0 - 0.1, player.lastZ) == 0) {
swimmingVelocities.add(vector.returnNewModified(vector.vector, VectorData.VectorType.SurfaceSwimming));
swimmingVelocities.add(vector.returnNewModified(vector.vectorX, vector.vectorY, vector.vectorZ, VectorData.VectorType.SurfaceSwimming));
}
return swimmingVelocities;
@@ -72,17 +79,21 @@ public class PredictionEngineWater extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
final double startX = vector.vectorX;
final double startY = vector.vectorY;
final double startZ = vector.vectorZ;
if (player.couldSkipTick && vector.isZeroPointZeroThree()) {
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), vector.vector.clone().getY(), vector.vector.clone().getY() + 0.05f);
existingVelocities.add(new VectorData(vector.vector.clone().setY(extraVelFromVertTickSkipUpwards), vector, VectorData.VectorType.Jump));
double extraVelFromVertTickSkipUpwards = GrimMath.clamp(player.actualMovement.getY(), startY, startY + 0.05f);
existingVelocities.add(new VectorData(startX, extraVelFromVertTickSkipUpwards, startZ, vector, VectorData.VectorType.Jump));
} else {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(startX, startY + 0.04f, startZ, vector, VectorData.VectorType.Jump));
}
if (player.slightlyTouchingWater && player.lastOnGround && !player.onGround) {
Vector3dm withJump = vector.vector.clone();
super.doJump(player, withJump);
existingVelocities.add(new VectorData(withJump, vector, VectorData.VectorType.Jump));
double[] jumpValues = {startX, startY, startZ};
super.doJump(player, jumpValues);
existingVelocities.add(new VectorData(jumpValues[0], jumpValues[1], jumpValues[2], vector, VectorData.VectorType.Jump));
}
}
}
@@ -92,7 +103,7 @@ public class PredictionEngineWater extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
staticVectorEndOfTick(player, vector.vector, swimmingFriction, playerGravity, isFalling);
staticVectorEndOfTick(player, vector, swimmingFriction, playerGravity, isFalling);
}
}
@@ -101,7 +112,7 @@ public class PredictionEngineWater extends PredictionEngine {
// "hacky" climbing where player enters ladder within 0.03 movement (WHY DOES 0.03 EXIST???)
if (player.lastWasClimbing == 0 && player.pointThreeEstimator.isNearClimbable() && (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) || !Collisions.isEmpty(player, player.boundingBox.copy().expand(
player.clientVelocity.getX(), 0, player.clientVelocity.getZ()).expand(0.5, -SimpleCollisionBox.COLLISION_EPSILON, 0.5)))) {
player.lastWasClimbing = FluidFallingAdjustedMovement.getFluidFallingAdjustedMovement(player, playerGravity, isFalling, player.clientVelocity.clone().setY(0.2D * 0.8F)).getY();
player.lastWasClimbing = FluidFallingAdjustedMovement.getAdjustedY(player, playerGravity, isFalling, 0.2D * 0.8F);
}
Set<VectorData> baseVelocities = super.fetchPossibleStartTickVectors(player);
@@ -20,8 +20,8 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
// This is just the vanilla equation for legacy water movement
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float f, float f2) {
float lengthSquared = (float) inputVector.lengthSquared();
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float f, float f2) {
float lengthSquared = (float) GrimMath.lengthSquared(x, y, z);
if (lengthSquared >= 1.0E-4F) {
lengthSquared = (float) Math.sqrt(lengthSquared);
@@ -31,13 +31,15 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
}
lengthSquared = swimmingSpeed / lengthSquared;
inputVector.multiply(lengthSquared);
x *= lengthSquared;
y *= lengthSquared;
z *= lengthSquared;
float yawRadians = GrimMath.radians(player.yaw);
float sinResult = player.trigHandler.sin(yawRadians);
float cosResult = player.trigHandler.cos(yawRadians);
return new Vector3dm(inputVector.getX() * cosResult - inputVector.getZ() * sinResult,
inputVector.getY(), inputVector.getZ() * cosResult + inputVector.getX() * sinResult);
return new Vector3dm(x * cosResult - z * sinResult,
y, z * cosResult + x * sinResult);
}
return new Vector3dm();
@@ -46,11 +48,12 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
@Override
public void addJumpsToPossibilities(GrimPlayer player, Set<VectorData> existingVelocities) {
for (VectorData vector : new HashSet<>(existingVelocities)) {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.04f, 0)), vector, VectorData.VectorType.Jump));
final VectorData[] vectorsToProcess = existingVelocities.toArray(new VectorData[0]);
for (VectorData vector : vectorsToProcess) {
existingVelocities.add(new VectorData(vector.vectorX, vector.vectorY + 0.04f, vector.vectorZ, vector, VectorData.VectorType.Jump));
if (player.skippedTickInActualMovement) {
existingVelocities.add(new VectorData(vector.vector.clone().add(new Vector3dm(0, 0.02f, 0)), vector, VectorData.VectorType.Jump));
existingVelocities.add(new VectorData(vector.vectorX,vector.vectorY + 0.02f, vector.vectorZ, vector, VectorData.VectorType.Jump));
}
}
}
@@ -60,10 +63,12 @@ public class PredictionEngineWaterLegacy extends PredictionEngine {
super.endOfTick(player, playerGravity);
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vector.multiply(new Vector3dm(swimmingFriction, 0.8F, swimmingFriction));
vector.vectorX *= swimmingFriction;
vector.vectorY *= 0.8F;
vector.vectorZ *= swimmingFriction;
// Gravity
vector.vector.setY(vector.vector.getY() - 0.02D);
vector.vectorY -= 0.02D;
}
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.enums.BoatEntityStatus;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.Collisions;
@@ -146,7 +147,7 @@ public class PredictionEngineBoat extends PredictionEngine {
for (VectorData data : possibleVectors) {
// TODO: is this correct?
data.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
data.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
@@ -155,14 +156,41 @@ public class PredictionEngineBoat extends PredictionEngine {
// so if a player tries to move in both directions, a packet will
// show that the player is staying, but the boat will move anyway
if (player.vehicleData.vehicleForward == 0) {
Vector3dm vector = data.vector.clone();
controlBoat(player, vector, true);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
vectors.add(data.returnNewModified(vector, VectorData.VectorType.InputResult));
float f = 0.035F;
double x = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
double y = data.vectorY;
double z = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
x *= player.stuckSpeedMultiplier.getX();
y *= player.stuckSpeedMultiplier.getY();
z *= player.stuckSpeedMultiplier.getZ();
}
vectors.add(data.returnNewModified(x, y, z, VectorData.VectorType.InputResult));
}
controlBoat(player, data.vector, false);
if (applyStuckSpeed != 0) data.vector.multiply(player.stuckSpeedMultiplier);
float f;
if (player.vehicleData.vehicleHorizontal != 0) {
f = 0.005F;
if (player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
} else if (player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
} else {
f = 0.0F;
}
// TODO (Mutation) (Confirm if Correct)
// It seems possible for applyStuckSpeed = 0, and 1 to add the same vector to the set twice
// since we're mutating data.vectorX, Y, Z which is very likely incorrect but I don't know enough to say for certain
data.vectorX = data.vectorX + player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f;
data.vectorZ = data.vectorZ + (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f);
if (applyStuckSpeed != 0) {
data.vectorX *= player.stuckSpeedMultiplier.getX();
data.vectorY *= player.stuckSpeedMultiplier.getY();
data.vectorZ *= player.stuckSpeedMultiplier.getZ();
}
vectors.add(data);
}
}
@@ -176,7 +204,7 @@ public class PredictionEngineBoat extends PredictionEngine {
addFluidPushingToStartingVectors(player, vectors);
for (VectorData data : vectors) {
floatBoat(player, data.vector);
floatBoat(player, data);
}
return vectors;
@@ -197,7 +225,7 @@ public class PredictionEngineBoat extends PredictionEngine {
return false;
}
private void floatBoat(GrimPlayer player, Vector3dm vector) {
private void floatBoat(GrimPlayer player, VectorData vector) {
double d1 = player.hasGravity ? -0.04f : 0;
double d2 = 0.0D;
float invFriction = 0.05F;
@@ -208,7 +236,7 @@ public class PredictionEngineBoat extends PredictionEngine {
player.lastY = getWaterLevelAbove(player) - 0.5625F + 0.101D;
player.boundingBox = GetBoundingBox.getCollisionBoxForPlayer(player, player.lastX, player.lastY, player.lastZ);
player.actualMovement = new Vector3dm(player.x - player.lastX, player.y - player.lastY, player.z - player.lastZ);
vector.setY(0);
vector.vectorY = 0;
player.vehicleData.lastYd = 0.0D;
player.vehicleData.status = BoatEntityStatus.IN_WATER;
@@ -229,12 +257,12 @@ public class PredictionEngineBoat extends PredictionEngine {
player.vehicleData.landFriction /= 2.0F;
}
vector.setX(vector.getX() * invFriction);
vector.setY(vector.getY() + d1);
vector.setZ(vector.getZ() * invFriction);
vector.vectorX *= invFriction;
vector.vectorY += d1;
vector.vectorZ *= invFriction;
if (d2 > 0.0D) {
double yVel = vector.getY();
double yVel = vector.vectorY;
vector.setY((yVel + d2 * 0.06153846016296973D) * 0.75D);
}
}
@@ -272,22 +300,4 @@ public class PredictionEngineBoat extends PredictionEngine {
return (float) (l + 1);
}
private void controlBoat(GrimPlayer player, Vector3dm vector, boolean intermediate) {
float f = 0.0F;
if (player.vehicleData.vehicleHorizontal != 0 && (!intermediate && player.vehicleData.vehicleForward == 0)) {
f += 0.005F;
}
//player.boatData.yRot += player.boatData.deltaRotation;
if (intermediate || player.vehicleData.vehicleForward > 0.1) {
f += 0.04F;
}
if (intermediate || player.vehicleData.vehicleForward < -0.01) {
f -= 0.005F;
}
vector.add(new Vector3dm(player.trigHandler.sin(GrimMath.radians(-player.yaw)) * f, 0, (double) (player.trigHandler.cos(GrimMath.radians(player.yaw)) * f)));
}
}
@@ -18,9 +18,9 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
@Override
public void endOfTick(GrimPlayer player, double delta) {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
vector.vector.setX(vector.vector.getX() * multiplier);
vector.vector.setY(vector.vector.getY() * multiplier);
vector.vector.setZ(vector.vector.getZ() * multiplier);
vector.vectorX *= multiplier;
vector.vectorY *= multiplier;
vector.vectorZ *= multiplier;
}
}
@@ -30,15 +30,15 @@ public class PredictionEngineHappyGhast extends PredictionEngineNormal {
}
@Override
public Vector3dm getMovementResultFromInput(GrimPlayer player, Vector3dm inputVector, float flyingSpeed, float yRot) {
public Vector3dm getMovementResultFromInput(GrimPlayer player, double x, double y, double z, float flyingSpeed, float yRot) {
float yRotRadians = GrimMath.radians(yRot);
float sin = player.trigHandler.sin(yRotRadians);
float cos = player.trigHandler.cos(yRotRadians);
double xResult = inputVector.getX() * cos - inputVector.getZ() * sin;
double zResult = inputVector.getZ() * cos + inputVector.getX() * sin;
double xResult = x * cos - z * sin;
double zResult = z * cos + x * sin;
return new Vector3dm(xResult * flyingSpeed, inputVector.getY() * flyingSpeed, zResult * flyingSpeed);
return new Vector3dm(xResult * flyingSpeed, y * flyingSpeed, zResult * flyingSpeed);
}
}
@@ -7,6 +7,7 @@ import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.packetentity.PacketEntityCamel;
import ac.grim.grimac.utils.data.packetentity.PacketEntityHorse;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import ac.grim.grimac.utils.nmsutil.BlockProperties;
import ac.grim.grimac.utils.nmsutil.JumpPower;
@@ -59,10 +60,12 @@ public final class PredictionEngineRideableUtils {
}
final double multiplier = (double) (22.2222F * player.vehicleData.horseJump) * camel.getAttributeValue(Attributes.MOVEMENT_SPEED) * (double) BlockProperties.getBlockSpeedFactor(player, player.mainSupportingBlockData, new Vector3d(player.lastX, player.lastY, player.lastZ));
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(new Vector3dm(1.0, 0.0, 1.0)).normalize().multiply(multiplier).add(new Vector3dm(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0));
Vector3dm jumpVelocity = ReachUtils.getLook(player, player.yaw, player.pitch).multiply(1.0, 0.0, 1.0).normalize().multiply(multiplier).add(0, (double) (1.4285F * player.vehicleData.horseJump) * jumpYVelocity, 0);
for (VectorData vectorData : possibleVectors) {
vectorData.vector.add(jumpVelocity);
vectorData.vectorX += jumpVelocity.getX();
vectorData.vectorY += jumpVelocity.getY();
vectorData.vectorZ += jumpVelocity.getZ();
}
player.vehicleData.horseJumping = true;
@@ -102,9 +105,10 @@ public final class PredictionEngineRideableUtils {
float f3 = player.trigHandler.cos(yawRadians);
for (VectorData vectorData : possibleVectors) {
vectorData.vector.setY(jumpVelocity);
vectorData.vectorY = jumpVelocity;
if (forwardInput > 0.0F) {
vectorData.vector.add(new Vector3dm(-0.4F * f2 * player.vehicleData.horseJump, 0.0D, 0.4F * f3 * player.vehicleData.horseJump));
vectorData.vectorX += -0.4F * f2 * player.vehicleData.horseJump;
vectorData.vectorZ += 0.4F * f3 * player.vehicleData.horseJump;
}
}
@@ -122,22 +126,24 @@ public final class PredictionEngineRideableUtils {
for (int applyStuckSpeed = 1; applyStuckSpeed >= 0; applyStuckSpeed--) {
if (applyStuckSpeed == 0 && player.isForceStuckSpeed()) break;
VectorData result = new VectorData(possibleLastTickOutput.vector.clone().add(predictionEngine.getMovementResultFromInput(player, movementVector, speed, player.yaw)), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
Vector3dm vector = result.vector.clone();
Vector3dm vector3dm = predictionEngine.getMovementResultFromInput(player, movementVector.getX(), movementVector.getY(), movementVector.getZ(), speed, player.yaw).add(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ);
VectorData result = new VectorData(vector3dm, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
Vector3dm vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
returnVectors.add(result);
// This is the laziest way to reduce false positives such as horse rearing
// No bypasses can ever be derived from this, so why not?
result = new VectorData(possibleLastTickOutput.vector.clone(), possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vector3dm(player.vehicleData.vehicleForward, 0, player.vehicleData.vehicleHorizontal);
vector = result.vector.clone();
result = new VectorData(possibleLastTickOutput.vectorX, possibleLastTickOutput.vectorY, possibleLastTickOutput.vectorZ, possibleLastTickOutput, VectorData.VectorType.InputResult);
result.input = new Vec2d(player.vehicleData.vehicleForward, player.vehicleData.vehicleHorizontal);
vector = new Vector3dm(result.vectorX, result.vectorY, result.vectorZ);
if (applyStuckSpeed != 0) vector.multiply(player.stuckSpeedMultiplier);
result = result.returnNewModified(vector, VectorData.VectorType.StuckMultiplier);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vector.clone(), player), VectorData.VectorType.Climbable);
result = result.returnNewModified(new PredictionEngineNormal().handleOnClimbable(result.vectorX, result.vectorY, result.vectorZ, player), VectorData.VectorType.Climbable);
returnVectors.add(result);
}
}
@@ -30,6 +30,14 @@ public class MessageUtil {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
public @NotNull String toUnlabeledString(int x, int y, int z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabeledString(double x, double y, double z) {
return x + ", " + y + ", " + z;
}
public @NotNull String toUnlabledString(@Nullable Vector3f vec) {
return vec == null ? "null" : vec.x + ", " + vec.y + ", " + vec.z;
}
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -14,7 +14,9 @@ import java.util.ArrayList;
import java.util.List;
public final class BlockBreak {
public final Vector3i position;
public final int x;
public final int y;
public final int z;
public final BlockFace face;
public final int faceId;
public final DiggingAction action;
@@ -24,9 +26,11 @@ public final class BlockBreak {
@Getter
private boolean cancelled;
public BlockBreak(GrimPlayer player, Vector3i position, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
public BlockBreak(GrimPlayer player, final int x, final int y, final int z, BlockFace face, int faceId, DiggingAction action, int sequence, WrappedBlockState block) {
this.player = player;
this.position = position;
this.x = x;
this.y = y;
this.z = z;
this.face = face;
this.faceId = faceId;
this.action = action;
@@ -39,12 +43,12 @@ public final class BlockBreak {
}
public SimpleCollisionBox getCombinedBox() {
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, position.x, position.y, position.z);
CollisionBox placedOn = HitboxData.getBlockHitbox(player, player.inventory.getHeldItem().getType().getPlacedType(), player.getClientVersion(), block, true, x, y, z);
List<SimpleCollisionBox> boxes = new ArrayList<>();
placedOn.downCast(boxes);
SimpleCollisionBox combined = new SimpleCollisionBox(position.x, position.y, position.z);
SimpleCollisionBox combined = new SimpleCollisionBox(x, y, z);
for (SimpleCollisionBox box : boxes) {
double minX = Math.max(box.minX, combined.minX);
double minY = Math.max(box.minY, combined.minY);
@@ -568,8 +568,10 @@ public class BlockPlace {
// This happens due to the lack of an idle packet on 1.9+ clients
// On 1.8 clients this should practically never happen
if (interpWidth - width > 0.05 || interpHeight - height > 0.05) {
Vector3d entityPos = entity.trackedServerPosition.getPos();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, entityPos.getX(), entityPos.getY(), entityPos.getZ(), entity);
final double x = entity.trackedServerPosition.getX();
final double y = entity.trackedServerPosition.getX();
final double z = entity.trackedServerPosition.getX();
interpBox = GetBoundingBox.getPacketEntityBoundingBox(player, x, y, z, entity);
}
if (box.isIntersected(interpBox)) {
@@ -641,9 +643,13 @@ public class BlockPlace {
Vector3dm look = ReachUtils.getLook(player, player.yaw, player.pitch);
final double distance = player.compensatedEntities.self.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE) + 3;
Vector3dm eyePos = new Vector3dm(player.x, player.y + player.getEyeHeight(), player.z);
Vector3dm endReachPos = eyePos.clone().add(new Vector3dm(look.getX() * distance, look.getY() * distance, look.getZ() * distance));
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyePos, endReachPos).first();
final double eyeX = player.x;
final double eyeY = player.y + player.getEyeHeight();
final double eyeZ = player.z;
final double endX = eyeX + look.getX() * distance;
final double endY = eyeY + look.getY() * distance;
final double endZ = eyeZ + look.getZ() * distance;
Vector3dm intercept = ReachUtils.calculateIntercept(box, eyeX, eyeY, eyeZ, endX, endY, endZ).first();
// Bring this back to relative to the block
// The player didn't even click the block... (we should force resync BEFORE we get here!)
@@ -11,7 +11,12 @@ import lombok.Setter;
@Getter
@Setter
public final class PositionUpdate {
private final Vector3d from, to;
private final double fromX;
private final double fromY;
private final double fromZ;
private final double toX;
private final double toY;
private final double toZ;
private final boolean onGround;
private final SetBackData setback;
private final TeleportData teleportData;
@@ -1,5 +1,3 @@
package ac.grim.grimac.utils.anticheat.update;
import com.github.retrooper.packetevents.util.Vector3d;
public record VehiclePositionUpdate(Vector3d from, Vector3d to, float xRot, float yRot, boolean isTeleport) {}
public record VehiclePositionUpdate(double fromX, double fromY, double fromZ, double toX, double toY, double toZ, float xRot, float yRot, boolean isTeleport) {}
@@ -7,15 +7,17 @@ import org.jetbrains.annotations.NotNull;
public record BlockModification(
WrappedBlockState oldBlockContents,
WrappedBlockState newBlockContents,
Vector3i location,
int x,
int y,
int z,
int tick,
Cause cause
) {
@Override
public @NotNull String toString() {
return String.format(
"BlockModification{location=%s, old=%s, new=%s, tick=%d, cause=%s}",
location, oldBlockContents, newBlockContents, tick, cause
"BlockModification{x=%d, y=%d, z=%d, old=%s, new=%s, tick=%d, cause=%s}",
x, y, z, oldBlockContents, newBlockContents, tick, cause
);
}
@@ -1018,6 +1018,30 @@ public enum CollisionData implements CollisionFactory {
}
}, StateTypes.DRIED_GHAST),
SHELF((player, version, data, x, y, z) -> {
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
// ViaVersion replacement block (planks)
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
}
return switch (data.getFacing()) {
case NORTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 11, 16, 16, 13), new HexCollisionBox(0, 0, 13, 16, 16, 16), new HexCollisionBox(0, 0, 11, 16, 4, 13));
case SOUTH -> new ComplexCollisionBox(3, new HexCollisionBox(0, 12, 3, 16, 16, 5), new HexCollisionBox(0, 0, 0, 16, 16, 3), new HexCollisionBox(0, 0, 3, 16, 4, 5));
case WEST -> new ComplexCollisionBox(3, new HexCollisionBox(11, 12, 0, 13, 16, 16), new HexCollisionBox(13, 0, 0, 16, 16, 16), new HexCollisionBox(11, 0, 0, 13, 4, 16));
case EAST -> new ComplexCollisionBox(3, new HexCollisionBox(3, 12, 0, 5, 16, 16), new HexCollisionBox(0, 0, 0, 3, 16, 16), new HexCollisionBox(3, 0, 0, 5, 4, 16));
default -> throw new IllegalStateException("Unexpected value: " + data.getFacing());
};
}, BlockTags.WOODEN_SHELVES.getStates().toArray(new StateType[0])),
COPPER_GOLEM_STATUE((player, version, data, x, y, z) -> {
if (version.isOlderThan(ClientVersion.V_1_21_9)) {
// ViaVersion replacement block (copper block)
return new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true);
}
return new HexCollisionBox(3, 0, 3, 13, 14, 13);
}, BlockTags.COPPER_GOLEM_STATUES.getStates().toArray(new StateType[0])),
DEFAULT(new SimpleCollisionBox(0, 0, 0, 1, 1, 1, true), StateTypes.STONE);
// This should be an array... but a hashmap will do for now...
@@ -1049,7 +1073,7 @@ public enum CollisionData implements CollisionFactory {
this.materials = mList.toArray(new StateType[0]);
}
private static CollisionBox getAmethystBox(ClientVersion version, com.github.retrooper.packetevents.protocol.world.BlockFace facing, int param_0, int param_1) {
private static CollisionBox getAmethystBox(ClientVersion version, BlockFace facing, int param_0, int param_1) {
if (version.isOlderThanOrEquals(ClientVersion.V_1_16_4))
return NoCollisionBox.INSTANCE;
@@ -1146,7 +1170,11 @@ public enum CollisionData implements CollisionFactory {
// Would pre-computing all states be worth the memory cost? I doubt it
public static CollisionData getData(StateType state) { // TODO: Find a better hack for lava and scaffolding
// What the fuck mojang, why put noCollision() and then give PITCHER_CROP collision?
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING || state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE || state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state) ? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
return state.isSolid() || state == StateTypes.LAVA || state == StateTypes.SCAFFOLDING
|| state == StateTypes.PITCHER_CROP || state == StateTypes.HEAVY_CORE
|| state == StateTypes.PALE_MOSS_CARPET || BlockTags.WALL_HANGING_SIGNS.contains(state)
|| BlockTags.COPPER_GOLEM_STATUES.contains(state)
? rawLookupMap.getOrDefault(state, DEFAULT) : NO_COLLISION;
}
// TODO: This is wrong if a block doesn't have any hitbox and isn't specified, light block?
@@ -56,7 +56,7 @@ public class SimpleCollisionBox implements CollisionBox {
}
public SimpleCollisionBox(Vector3i pos) {
this(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
this(pos.getX(), pos.getY(), pos.getZ());
}
// If you want to set a full block from a point
@@ -412,16 +412,16 @@ public class SimpleCollisionBox implements CollisionBox {
// Copied from hawk lol
// I would like to point out that this is magic to me and I have not attempted to understand this code
public Vector3dm intersectsRay(Ray ray, float minDist, float maxDist) {
Vector3dm invDir = new Vector3dm(1f / ray.getDirection().getX(), 1f / ray.getDirection().getY(), 1f / ray.getDirection().getZ());
Vector3dm invDir = new Vector3dm(1f / ray.getDirX(), 1f / ray.getDirY(), 1f / ray.getDirZ());
boolean signDirX = invDir.getX() < 0;
boolean signDirY = invDir.getY() < 0;
boolean signDirZ = invDir.getZ() < 0;
double tmin = ((signDirX ? maxX : minX) - ray.getOrigin().getX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOrigin().getX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOrigin().getY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOrigin().getY()) * invDir.getY();
double tmin = ((signDirX ? maxX : minX) - ray.getOriginX()) * invDir.getX();
double tmax = ((signDirX ? minX : maxX) - ray.getOriginX()) * invDir.getX();
double tymin = ((signDirY ? maxY : minY) - ray.getOriginY()) * invDir.getY();
double tymax = ((signDirY ? minY : maxY) - ray.getOriginY()) * invDir.getY();
if (tmin > tymax || tymin > tmax) {
return null;
@@ -430,8 +430,8 @@ public class SimpleCollisionBox implements CollisionBox {
if (tymin > tmin) tmin = tymin;
if (tymax < tmax) tmax = tymax;
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOrigin().getZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOrigin().getZ()) * invDir.getZ();
double tzmin = ((signDirZ ? maxZ : minZ) - ray.getOriginZ()) * invDir.getZ();
double tzmax = ((signDirZ ? minZ : maxZ) - ray.getOriginZ()) * invDir.getZ();
if ((tmin > tzmax) || (tzmin > tmax)) {
return null;
@@ -39,18 +39,17 @@ public class ReachInterpolationData {
private boolean expandNonRelative = false;
private int cancelledLerpInterpolationStepsLowBound = Integer.MAX_VALUE;
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, TrackedPosition position, PacketEntity entity) {
final boolean isPointNine = !player.inVehicle() && player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9);
public ReachInterpolationData(GrimPlayer player, SimpleCollisionBox startingLocation, final double x, final double y, final double z, PacketEntity entity) {
final boolean unreliableTicking = !player.inVehicle() && player.canSkipTicks();
this.startingLocation = startingLocation;
final Vector3d pos = position.getPos();
this.targetLocation = new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false);
this.targetLocation = new SimpleCollisionBox(x, y, z, x, y, z, false);
this.player = player;
this.entity = entity;
// 1.9 -> 1.8 precision loss in packets
// (ViaVersion is doing some stuff that makes this code difficult)
if (!isPointNine && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9) && PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_9)) {
targetLocation.expand(0.03125);
}
@@ -66,7 +65,8 @@ public class ReachInterpolationData {
interpolationSteps = 1;
}
if (isPointNine) interpolationStepsHighBound = getInterpolationSteps();
// If the player doesn't tick reliably, their interpolation is anywhere between min and max steps.
if (unreliableTicking) interpolationStepsHighBound = getInterpolationSteps();
}
// While riding entities, there is no interpolation.
@@ -11,22 +11,34 @@ import lombok.ToString;
public class SetBackData {
private final TeleportData teleportData;
private final float xRot, yRot;
private final Vector3dm velocity;
private final double velocityX, velocityY, velocityZ;
private boolean hasVelocity;
private final boolean vehicle;
private boolean isComplete = false;
// TODO: Rethink when we block movements for teleports, perhaps after 10 ticks or 5 blocks?
private boolean isPlugin;
private int ticksComplete = 0;
public SetBackData(TeleportData teleportData, float xRot, float yRot, Vector3dm velocity, boolean vehicle, boolean isPlugin) {
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean hasVelocity, boolean vehicle, boolean isPlugin) {
this.teleportData = teleportData;
this.xRot = xRot;
this.yRot = yRot;
this.velocity = velocity;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.velocityZ = velocityZ;
this.hasVelocity = hasVelocity;
this.vehicle = vehicle;
this.isPlugin = isPlugin;
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, double velocityX, double velocityY, double velocityZ, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, velocityX, velocityY, velocityZ, true, vehicle, isPlugin);
}
public SetBackData(TeleportData teleportData, float xRot, float yRot, boolean vehicle, boolean isPlugin) {
this(teleportData, xRot, yRot, 0, 0, 0, false, vehicle, isPlugin);
}
public void tick() {
if (isComplete) ticksComplete++;
}
@@ -12,9 +12,13 @@ import org.jetbrains.annotations.NotNull;
@AllArgsConstructor
@Getter
public class TeleportData {
private final Vector3d location;
private final Vector3d velocity;
private final RelativeFlag flags;
private final double locationX;
private final double locationY;
private final double locationZ;
private final double velocityX;
private final double velocityY;
private final double velocityZ;
private final @NotNull RelativeFlag flags;
@Setter
private int transaction;
@Setter
@@ -36,29 +40,27 @@ public class TeleportData {
if (!isRelativeZ()) {
vector.setZ(0);
}
}
if (velocity != null && isStupidTeleportSystem) {
} else {
// WHAT WAS MOJANG THINKING MAKING TELEPORTS A REPLACEMENT PACKET FOR EXPLOSION VELOCITY /s
if (isRelativeDeltaX()) {
vector.setX(vector.getX() + velocity.getX());
vector.setX(vector.getX() + velocityX);
} else {
vector.setX(velocity.getX());
vector.setX(velocityX);
}
if (isRelativeDeltaY()) {
vector.setY(vector.getY() + velocity.getY());
vector.setY(vector.getY() + velocityY);
} else {
vector.setY(velocity.getY());
vector.setY(velocityY);
// Is this correct? Don't know don't care.
player.lastWasClimbing = 0; // Vertical movement reset
player.canSwimHop = false; // Vertical movement reset
}
if (isRelativeDeltaZ()) {
vector.setZ(vector.getZ() + velocity.getZ());
vector.setZ(vector.getZ() + velocityZ);
} else {
vector.setZ(velocity.getZ());
vector.setZ(velocityZ);
}
}
}
@@ -11,8 +11,9 @@ public final class TrackedPosition {
private static final double LEGACY_COORDINATE_SCALE = 32.0;
private final double scale;
@Setter
private Vector3d pos = new Vector3d();
private double x;
private double y;
private double z;
public TrackedPosition() {
// this.scale = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_9) ? MODERN_COORDINATE_SCALE : LEGACY_COORDINATE_SCALE;
@@ -35,23 +36,38 @@ public final class TrackedPosition {
return value / scale;
}
// Method since 1.16.
public Vector3d withDelta(long x, long y, long z) {
if (x == 0L && y == 0L && z == 0L) {
return this.pos;
}
double d = x == 0L ? this.pos.x : unpack(pack(this.pos.x, scale) + x);
double e = y == 0L ? this.pos.y : unpack(pack(this.pos.y, scale) + y);
double f = z == 0L ? this.pos.z : unpack(pack(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
// Methods since 1.16.
public double withDeltaX(long x) {
if (x == 0L) return this.x;
return unpack(pack(this.x, scale) + x);
}
// In 1.16-, this was different.
public Vector3d withDeltaLegacy(double x, double y, double z) {
double d = unpackLegacy(packLegacy(this.pos.x, scale) + x);
double e = unpackLegacy(packLegacy(this.pos.y, scale) + y);
double f = unpackLegacy(packLegacy(this.pos.z, scale) + z);
return new Vector3d(d, e, f);
public double withDeltaY(long y) {
if (y == 0L) return this.y;
return unpack(pack(this.y, scale) + y);
}
public double withDeltaZ(long z) {
if (z == 0L) return this.z;
return unpack(pack(this.z, scale) + z);
}
// In 1.16-, these were different.
public double withDeltaLegacyX(double x) {
return unpackLegacy(packLegacy(this.x, scale) + x);
}
public double withDeltaLegacyY(double y) {
return unpackLegacy(packLegacy(this.y, scale) + y);
}
public double withDeltaLegacyZ(double z) {
return unpackLegacy(packLegacy(this.z, scale) + z);
}
public void setPos(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
}
@@ -1,5 +1,7 @@
package ac.grim.grimac.utils.data;
import ac.grim.grimac.utils.anticheat.MessageUtil;
import ac.grim.grimac.utils.math.Vec2d;
import ac.grim.grimac.utils.math.Vector3dm;
import lombok.Getter;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@@ -10,15 +12,33 @@ public class VectorData {
public final VectorType vectorType;
public VectorData lastVector;
public VectorData preUncertainty;
public Vector3dm vector;
public @MonotonicNonNull Vector3dm input;
public double vectorX, vectorY, vectorZ;
public @MonotonicNonNull Vec2d input;
@Getter
private boolean isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow = false;
// For handling replacing the type of vector it is while keeping data
public VectorData(Vector3dm vector, VectorData lastVector, VectorType vectorType) {
this.vector = vector;
this(vector.getX(), vector.getY(), vector.getZ(), lastVector, vectorType);
}
public void setX(double x) {
this.vectorX = x;
}
public void setY(double y) {
this.vectorY = y;
}
public void setZ(double z) {
this.vectorZ = z;
}
public VectorData(final double x, final double y, final double z, VectorData lastVector, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.lastVector = lastVector;
this.vectorType = vectorType;
@@ -42,30 +62,40 @@ public class VectorData {
}
public VectorData(Vector3dm vector, VectorType vectorType) {
this.vector = vector;
this(vector.getX(), vector.getY(), vector.getZ(), vectorType);
}
public VectorData(final double x, final double y, final double z, VectorType vectorType) {
this.vectorX = x;
this.vectorY = y;
this.vectorZ = z;
this.vectorType = vectorType;
addVectorType(vectorType);
}
public VectorData returnNewModified(VectorType type) {
return new VectorData(vector, this, type);
return new VectorData(this.vectorX, this.vectorY, this.vectorZ, this, type);
}
public VectorData returnNewModified(Vector3dm newVec, VectorType type) {
return new VectorData(newVec, this, type);
}
public VectorData returnNewModified(final double x, final double y, final double z, VectorType type) {
return new VectorData(x, y, z, this, type);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VectorData that = (VectorData) o;
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && Objects.equals(vector, that.vector);
return isKnockback == that.isKnockback && firstBreadKb == that.firstBreadKb && isExplosion == that.isExplosion && firstBreadExplosion == that.firstBreadExplosion && isTrident == that.isTrident && isZeroPointZeroThree == that.isZeroPointZeroThree && isSwimHop == that.isSwimHop && isFlipSneaking == that.isFlipSneaking && isFlipItem == that.isFlipItem && isJump == that.isJump && isAttackSlow == that.isAttackSlow && vectorType == that.vectorType && Objects.equals(lastVector, that.lastVector) && Objects.equals(preUncertainty, that.preUncertainty) && vectorX == that.vectorX && vectorY == that.vectorY && vectorZ == that.vectorZ;
}
@Override
public int hashCode() {
return Objects.hash(vectorType, lastVector, preUncertainty, vector, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
return Objects.hash(vectorType, lastVector, preUncertainty, vectorX, vectorY, vectorZ, isKnockback, firstBreadKb, isExplosion, firstBreadExplosion, isTrident, isZeroPointZeroThree, isSwimHop, isFlipSneaking, isFlipItem, isJump, isAttackSlow);
}
public void addVectorType(VectorType type) {
@@ -88,7 +118,7 @@ public class VectorData {
public String toString() {
return "VectorData{" +
"pointThree=" + isZeroPointZeroThree +
", vector=" + vector +
", vector=" + MessageUtil.toUnlabeledString(vectorX, vectorY, vectorZ) +
'}';
}
@@ -20,9 +20,11 @@ public class VehicleData {
public float vehicleForward = 0f;
public boolean lastDummy = false;
public boolean wasVehicleSwitch = false;
public final ConcurrentLinkedQueue<Pair<Integer, Vector3d>> vehicleTeleports = new ConcurrentLinkedQueue<>();
public final ConcurrentLinkedQueue<VehicleTeleport> vehicleTeleports = new ConcurrentLinkedQueue<>();
public float nextHorseJump = 0;
public float horseJump = 0;
public boolean horseJumping = false;
public int camelDashCooldown;
public record VehicleTeleport(int teleportId, double x, double y, double z) {}
}
@@ -73,12 +73,14 @@ public class PacketEntity extends TypedPacketEntity {
this.uuid = uuid;
initAttributes(player);
this.trackedServerPosition = new TrackedPosition();
this.trackedServerPosition.setPos(new Vector3d(x, y, z));
this.trackedServerPosition.setPos(x, y, z);
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) { // Thanks ViaVersion
trackedServerPosition.setPos(new Vector3d(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d));
trackedServerPosition.setPos(((int) (x * 32)) / 32d, ((int) (y * 32)) / 32d, ((int) (z * 32)) / 32d);
}
final Vector3d pos = trackedServerPosition.getPos();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z, false), trackedServerPosition, this);
final double xPos = trackedServerPosition.getX();
final double yPos = trackedServerPosition.getY();
final double zPos = trackedServerPosition.getZ();
this.newPacketLocation = new ReachInterpolationData(player, new SimpleCollisionBox(xPos, yPos, zPos, xPos, yPos, zPos, false), xPos, yPos, zPos, this);
}
protected void trackAttribute(ValuedAttribute valuedAttribute) {
@@ -129,25 +131,31 @@ public class PacketEntity extends TypedPacketEntity {
if (relative) {
// This only matters for 1.9+ clients, but it won't hurt 1.8 clients either... align for imprecision
final double scale = trackedServerPosition.getScale();
Vector3d vec3d;
final double x;
final double y;
final double z;
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_16)) {
vec3d = trackedServerPosition.withDelta(TrackedPosition.pack(relX, scale), TrackedPosition.pack(relY, scale), TrackedPosition.pack(relZ, scale));
x = trackedServerPosition.withDeltaX(TrackedPosition.pack(relX, scale));
y = trackedServerPosition.withDeltaY(TrackedPosition.pack(relY, scale));
z = trackedServerPosition.withDeltaZ(TrackedPosition.pack(relZ, scale));
} else {
vec3d = trackedServerPosition.withDeltaLegacy(TrackedPosition.packLegacy(relX, scale), TrackedPosition.packLegacy(relY, scale), TrackedPosition.packLegacy(relZ, scale));
x = trackedServerPosition.withDeltaLegacyX(TrackedPosition.packLegacy(relX, scale));
y = trackedServerPosition.withDeltaLegacyY(TrackedPosition.packLegacy(relY, scale));
z = trackedServerPosition.withDeltaLegacyZ(TrackedPosition.packLegacy(relZ, scale));
}
trackedServerPosition.setPos(vec3d);
trackedServerPosition.setPos(x, y, z);
} else {
trackedServerPosition.setPos(new Vector3d(relX, relY, relZ));
trackedServerPosition.setPos(relX, relY, relZ);
// ViaVersion desync's here for teleports
// It simply teleports the entity with its position divided by 32... ignoring the offset this causes.
// Thanks a lot ViaVersion! Please don't fix this, or it will be a pain to support.
if (player.getClientVersion().isOlderThan(ClientVersion.V_1_9)) {
trackedServerPosition.setPos(new Vector3d(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d));
trackedServerPosition.setPos(((int) (relX * 32)) / 32d, ((int) (relY * 32)) / 32d, ((int) (relZ * 32)) / 32d);
}
}
}
this.oldPacketLocation = newPacketLocation;
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition, this);
this.newPacketLocation = new ReachInterpolationData(player, oldPacketLocation.getPossibleLocationCombined(), trackedServerPosition.getX(), trackedServerPosition.getY(), trackedServerPosition.getZ(), this);
// TODO make config option to rewrite Rots to PosRots instead of expanding to handle this false
// https://bugs.mojang.com/browse/MC-255263
@@ -208,7 +216,7 @@ public class PacketEntity extends TypedPacketEntity {
public void setPositionRaw(GrimPlayer player, SimpleCollisionBox box) {
// I'm disappointed in you mojang. Please don't set the packet position as it desyncs it...
// But let's follow this flawed client-sided logic!
this.trackedServerPosition.setPos(new Vector3d((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ));
this.trackedServerPosition.setPos((box.maxX - box.minX) / 2 + box.minX, box.minY, (box.maxZ - box.minZ) / 2 + box.minZ);
// This disables interpolation
this.newPacketLocation = new ReachInterpolationData(player, box, this);
}
@@ -0,0 +1,8 @@
package ac.grim.grimac.utils.function;
import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState;
@FunctionalInterface
public interface BlockStateAtPosPredicate {
boolean test(WrappedBlockState state, int x, int y, int z);
}
@@ -0,0 +1,6 @@
package ac.grim.grimac.utils.function;
@FunctionalInterface
public interface Int3Consumer {
void accept(int x, int y, int z);
}
@@ -33,23 +33,26 @@ public enum EquipmentType {
}
if (item == ItemTypes.LEATHER_BOOTS || item == ItemTypes.CHAINMAIL_BOOTS
|| item == ItemTypes.IRON_BOOTS || item == ItemTypes.DIAMOND_BOOTS
|| item == ItemTypes.GOLDEN_BOOTS || item == ItemTypes.NETHERITE_BOOTS) {
|| item == ItemTypes.GOLDEN_BOOTS || item == ItemTypes.NETHERITE_BOOTS
|| item == ItemTypes.COPPER_BOOTS) {
return FEET;
}
if (item == ItemTypes.LEATHER_LEGGINGS || item == ItemTypes.CHAINMAIL_LEGGINGS
|| item == ItemTypes.IRON_LEGGINGS || item == ItemTypes.DIAMOND_LEGGINGS
|| item == ItemTypes.GOLDEN_LEGGINGS || item == ItemTypes.NETHERITE_LEGGINGS) {
|| item == ItemTypes.GOLDEN_LEGGINGS || item == ItemTypes.NETHERITE_LEGGINGS
|| item == ItemTypes.COPPER_LEGGINGS) {
return LEGS;
}
if (item == ItemTypes.LEATHER_CHESTPLATE || item == ItemTypes.CHAINMAIL_CHESTPLATE
|| item == ItemTypes.IRON_CHESTPLATE || item == ItemTypes.DIAMOND_CHESTPLATE
|| item == ItemTypes.GOLDEN_CHESTPLATE || item == ItemTypes.NETHERITE_CHESTPLATE) {
|| item == ItemTypes.GOLDEN_CHESTPLATE || item == ItemTypes.NETHERITE_CHESTPLATE
|| item == ItemTypes.COPPER_CHESTPLATE) {
return CHEST;
}
if (item == ItemTypes.LEATHER_HELMET || item == ItemTypes.CHAINMAIL_HELMET
|| item == ItemTypes.IRON_HELMET || item == ItemTypes.DIAMOND_HELMET
|| item == ItemTypes.GOLDEN_HELMET || item == ItemTypes.NETHERITE_HELMET
|| item == ItemTypes.TURTLE_HELMET) {
|| item == ItemTypes.COPPER_HELMET || item == ItemTypes.TURTLE_HELMET) {
return HEAD;
}
return ItemTypes.SHIELD == item ? OFFHAND : MAINHAND;
@@ -7,7 +7,6 @@ import ac.grim.grimac.utils.chunks.Column;
import ac.grim.grimac.utils.collisions.CollisionData;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.BlockPrediction;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.PistonData;
import ac.grim.grimac.utils.data.ShulkerData;
import ac.grim.grimac.utils.data.packetentity.PacketEntity;
@@ -55,6 +54,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import lombok.Getter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -83,14 +83,16 @@ public class CompensatedWorld {
// The owning list is so that this info can be removed when the final list is processed
private final Long2ObjectOpenHashMap<BlockPrediction> originalServerBlocks = new Long2ObjectOpenHashMap<>();
// Blocks the client changed while placing or breaking blocks
private List<Vector3i> currentlyChangedBlocks = new LinkedList<>();
private List<Vector3i> currentlyChangedBlocks = new ArrayList<>();
private final Int2ObjectMap<List<Vector3i>> serverIsCurrentlyProcessingThesePredictions = new Int2ObjectOpenHashMap<>();
private final Object2ObjectLinkedOpenHashMap<Pair<Vector3i, DiggingAction>, Vector3d> unackedActions = new Object2ObjectLinkedOpenHashMap<>();
private final Object2ObjectLinkedOpenHashMap<UnAckedAction, Vector3d> unackedActions = new Object2ObjectLinkedOpenHashMap<>();
private boolean isCurrentlyPredicting = false;
public boolean isRaining = false;
private final boolean noNegativeBlocks;
private record UnAckedAction(int x, int y, int z, DiggingAction action) {}
public CompensatedWorld(GrimPlayer player) {
this.player = player;
chunks = new Long2ObjectOpenHashMap<>(81, 0.5f);
@@ -114,15 +116,15 @@ public class CompensatedWorld {
}
}
public void handleBlockBreakAck(Vector3i blockPos, int blockState, DiggingAction action, boolean accepted) {
if (!accepted || action != DiggingAction.START_DIGGING || !unackedActions.containsKey(new Pair<>(blockPos, action))) {
public void handleBlockBreakAck(final int x, final int y, final int z, int blockState, DiggingAction action, boolean accepted) {
if (!accepted || action != DiggingAction.START_DIGGING || !unackedActions.containsKey(new UnAckedAction(x, y, z, action))) {
player.sendTransaction(); // This packet actually matters
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
Vector3d playerPos = unackedActions.remove(new Pair<>(blockPos, action));
handleAck(blockPos, blockState, playerPos);
Vector3d playerPos = unackedActions.remove(new UnAckedAction(x, y, z, action));
handleAck(x, y, z, blockState, playerPos);
});
} else {
unackedActions.remove(new Pair<>(blockPos, action));
unackedActions.remove(new UnAckedAction(x, y, z, action));
}
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> {
@@ -135,34 +137,38 @@ public class CompensatedWorld {
private void applyBlockChanges(List<Vector3i> toApplyBlocks) {
player.sendTransaction();
player.latencyUtils.addRealTimeTask(player.lastTransactionSent.get(), () -> toApplyBlocks.forEach(vector3i -> {
BlockPrediction predictionData = originalServerBlocks.get(vector3i.getSerializedPosition());
final int x = vector3i.getX();
final int y = vector3i.getY();
final int z = vector3i.getZ();
long serializedPosition = GrimMath.getSerializedPosition(x, y, z);
BlockPrediction predictionData = originalServerBlocks.get(serializedPosition);
// We are the last to care about this prediction, remove it to stop memory leak
// Block changes are allowed to execute out of order, because it actually doesn't matter
if (predictionData != null && predictionData.getForBlockUpdate() == toApplyBlocks) {
originalServerBlocks.remove(vector3i.getSerializedPosition());
handleAck(vector3i, predictionData.getOriginalBlockId(), predictionData.getPlayerPosition());
originalServerBlocks.remove(serializedPosition);
handleAck(x, y, z, predictionData.getOriginalBlockId(), predictionData.getPlayerPosition());
}
}));
}
private void handleAck(Vector3i vector3i, int originalBlockId, Vector3d playerPosition) {
private void handleAck(final int x, final int y, final int z, int originalBlockId, Vector3d playerPosition) {
// If we need to change the world block state
if (getBlock(vector3i).getGlobalId() != originalBlockId) {
if (getBlock(x, y , z).getGlobalId() != originalBlockId) {
player.blockHistory.add(
new BlockModification(
getBlock(vector3i),
getBlock(x, y ,z),
WrappedBlockState.getByGlobalId(originalBlockId),
vector3i,
x, y, z,
GrimAPI.INSTANCE.getTickManager().currentTick,
BlockModification.Cause.HANDLE_NETTY_SYNC_TRANSACTION
)
);
updateBlock(vector3i.getX(), vector3i.getY(), vector3i.getZ(), originalBlockId);
updateBlock(x, y, z, originalBlockId);
WrappedBlockState state = WrappedBlockState.getByGlobalId(blockVersion, originalBlockId);
// The player will teleport themselves if they get stuck in the reverted block
if (playerPosition != null && CollisionData.getData(state.getType()).getMovementCollisionBox(player, player.getClientVersion(), state, vector3i.getX(), vector3i.getY(), vector3i.getZ()).isIntersected(player.boundingBox)) {
if (playerPosition != null && CollisionData.getData(state.getType()).getMovementCollisionBox(player, player.getClientVersion(), state, x, y, z).isIntersected(player.boundingBox)) {
player.lastX = player.x;
player.lastY = player.y;
player.lastZ = player.z;
@@ -177,7 +183,11 @@ public class CompensatedWorld {
public void handleBlockBreakPrediction(WrapperPlayClientPlayerDigging digging) {
// 1.14.4 intentional and correct, do not change it to 1.14
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14_4) && player.getClientVersion().isOlderThanOrEquals(ClientVersion.V_1_18_2)) {
unackedActions.put(new Pair<>(digging.getBlockPosition(), digging.getAction()), new Vector3d(player.x, player.y, player.z));
Vector3i blockPosition = digging.getBlockPosition();
final int x = blockPosition.getX();
final int y = blockPosition.getY();
final int z = blockPosition.getZ();
unackedActions.put(new UnAckedAction(x, y, z, digging.getAction()), new Vector3d(player.x, player.y, player.z));
}
}
@@ -188,7 +198,7 @@ public class CompensatedWorld {
if (this.currentlyChangedBlocks.isEmpty()) return; // Nothing to change
List<Vector3i> toApplyBlocks = this.currentlyChangedBlocks; // We must now track the client applying the server predicted blocks
this.currentlyChangedBlocks = new LinkedList<>(); // Reset variable without changing original
this.currentlyChangedBlocks = new ArrayList<>(); // Reset variable without changing original
// We don't need to simulate any packets, it is native to the version we are on
if (PacketEvents.getAPI().getServerManager().getVersion().isNewerThanOrEquals(ServerVersion.V_1_19)) {
@@ -263,12 +273,13 @@ public class CompensatedWorld {
}
public void updateBlock(int x, int y, int z, int combinedID) {
Vector3i asVector = new Vector3i(x, y, z);
BlockPrediction prediction = originalServerBlocks.get(asVector.getSerializedPosition());
long serializedPosition = GrimMath.getSerializedPosition(x, y, z);
BlockPrediction prediction = originalServerBlocks.get(serializedPosition);
if (isCurrentlyPredicting) {
Vector3i asVector = new Vector3i(x , y, z);
if (prediction == null) {
originalServerBlocks.put(asVector.getSerializedPosition(), new BlockPrediction(currentlyChangedBlocks, asVector, getBlock(asVector).getGlobalId(), new Vector3d(player.x, player.y, player.z))); // Remember server controlled block type
originalServerBlocks.put(serializedPosition, new BlockPrediction(currentlyChangedBlocks, asVector, getBlock(x, y, z).getGlobalId(), new Vector3d(player.x, player.y, player.z))); // Remember server controlled block type
} else {
prediction.setForBlockUpdate(currentlyChangedBlocks); // Block existing there was placed by client, mark block to have a new prediction
}
@@ -626,7 +637,7 @@ public class CompensatedWorld {
}
public boolean containsLiquid(SimpleCollisionBox var0) {
return Collisions.hasMaterial(player, var0, data -> Materials.isWater(player.getClientVersion(), data.first()) || data.first().getType() == StateTypes.LAVA);
return Collisions.hasMaterial(player, var0, data -> Materials.isWater(player.getClientVersion(), data) || data.getType() == StateTypes.LAVA);
}
public double getLavaFluidLevelAt(int x, int y, int z) {
@@ -648,7 +659,7 @@ public class CompensatedWorld {
}
public boolean containsLava(SimpleCollisionBox var0) {
return Collisions.hasMaterial(player, var0, data -> data.first().getType() == StateTypes.LAVA);
return Collisions.hasMaterial(player, var0, data -> data.getType() == StateTypes.LAVA);
}
public double getWaterFluidLevelAt(double x, double y, double z) {
@@ -1,5 +1,7 @@
package ac.grim.grimac.utils.math;
import com.github.retrooper.packetevents.PacketEvents;
import com.github.retrooper.packetevents.manager.server.ServerVersion;
import com.github.retrooper.packetevents.util.Vector3i;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.Contract;
@@ -233,4 +235,38 @@ public class GrimMath {
public static double square(double num) {
return num * num;
}
public static double distanceSquared(double x1, double y1, double z1, double x2, double y2, double z2) {
return GrimMath.square(x1 - x2) + GrimMath.square(y1 - y2) + GrimMath.square(z1 - z2);
}
private static final ServerVersion serverVersion = PacketEvents.getAPI().getServerManager().getVersion();
public static long getSerializedPosition(int x, int y, int z) {
// 1.17 adds support for negative values
if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_17)) {
long lx = x & 0x3FFFFFF;
long ly = y & 0xFFF;
long lz = z & 0x3FFFFFF;
return lx << 38 | lz << 12 | ly;
// 1.14 method for this is storing X Z Y
} else if (serverVersion.isNewerThanOrEquals(ServerVersion.V_1_14)) {
return ((long) (x & 0x3FFFFFF) << 38) | ((long) (z & 0x3FFFFFF) << 12) | (y & 0xFFF);
}
// 1.13 and below store X Y Z
return ((long) (x & 0x3FFFFFF) << 38) | ((long) (y & 0xFFF) << 26) | (z & 0x3FFFFFF);
}
public static double lengthSquared(double x, double z) {
return GrimMath.square(x) + GrimMath.square(z);
}
public static double lengthSquared(double x, double y, double z) {
return GrimMath.square(x) + GrimMath.square(y) + GrimMath.square(z);
}
public static double length(double x, double y, double z) {
return Math.sqrt(lengthSquared(x, y, z));
}
}
@@ -0,0 +1,3 @@
package ac.grim.grimac.utils.math;
public record Vec2d(double x, double z) {}
@@ -66,23 +66,35 @@ public class Vector3dm implements Cloneable, Serializable {
}
public @NotNull Vector3dm add(@NotNull Vector3dm vec) {
this.x += vec.x;
this.y += vec.y;
this.z += vec.z;
return add(vec.x, vec.y, vec.z);
}
public @NotNull Vector3dm add(double x, double y, double z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public @NotNull Vector3dm subtract(@NotNull Vector3dm vec) {
this.x -= vec.x;
this.y -= vec.y;
this.z -= vec.z;
return subtract(vec.x, vec.y, vec.z);
}
public @NotNull Vector3dm subtract(double x, double y, double z) {
this.x -= x;
this.y -= y;
this.z -= z;
return this;
}
public @NotNull Vector3dm multiply(@NotNull Vector3dm vec) {
this.x *= vec.x;
this.y *= vec.y;
this.z *= vec.z;
return multiply(vec.x, vec.y, vec.z);
}
public @NotNull Vector3dm multiply(double x, double y, double z) {
this.x *= x;
this.y *= y;
this.z *= z;
return this;
}
@@ -109,11 +121,19 @@ public class Vector3dm implements Cloneable, Serializable {
}
public double distance(@NotNull Vector3dm o) {
return Math.sqrt(GrimMath.square(this.x - o.x) + GrimMath.square(this.y - o.y) + GrimMath.square(this.z - o.z));
return Math.sqrt(distanceSquared(o));
}
public double distanceSquared(@NotNull Vector3dm o) {
return GrimMath.square(this.x - o.x) + GrimMath.square(this.y - o.y) + GrimMath.square(this.z - o.z);
return distanceSquared(o.x, o.y, o.z);
}
public double distance(double oX, double oY, double oZ) {
return Math.sqrt(distanceSquared(oX, oY, oZ));
}
public double distanceSquared(double oX, double oY, double oZ) {
return GrimMath.square(this.x - oX) + GrimMath.square(this.y - oY) + GrimMath.square(this.z - oZ);
}
public @NotNull Vector3dm midpoint(@NotNull Vector3dm other) {
@@ -8,16 +8,20 @@ import org.jetbrains.annotations.NotNull;
@UtilityClass
public class VectorUtils {
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorToCutTo, @NotNull Vector3dm min, @NotNull Vector3dm max) {
SimpleCollisionBox box = new SimpleCollisionBox(min, max).sort();
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorToCutTo, double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
SimpleCollisionBox box = new SimpleCollisionBox(minX, minY, minZ, maxX, maxY, maxZ).sort();
return cutBoxToVector(vectorToCutTo, box);
}
@Contract("_, _ -> new")
public static @NotNull Vector3dm cutBoxToVector(@NotNull Vector3dm vectorCutTo, @NotNull SimpleCollisionBox box) {
return new Vector3dm(GrimMath.clamp(vectorCutTo.getX(), box.minX, box.maxX),
GrimMath.clamp(vectorCutTo.getY(), box.minY, box.maxY),
GrimMath.clamp(vectorCutTo.getZ(), box.minZ, box.maxZ));
return cutBoxToVector(vectorCutTo.getX(), vectorCutTo.getY(), vectorCutTo.getZ(), box);
}
public static @NotNull Vector3dm cutBoxToVector(double x, double y, double z, @NotNull SimpleCollisionBox box) {
return new Vector3dm(GrimMath.clamp(x, box.minX, box.maxX),
GrimMath.clamp(y, box.minY, box.maxY),
GrimMath.clamp(z, box.minZ, box.maxZ));
}
@Contract("_ -> new")
@@ -28,10 +32,14 @@ public class VectorUtils {
// Clamping stops the player from causing an integer overflow and crashing the netty thread
@Contract("_ -> new")
public static @NotNull Vector3d clampVector(@NotNull Vector3d toClamp) {
double x = GrimMath.clamp(toClamp.getX(), -3.0E7D, 3.0E7D);
double y = GrimMath.clamp(toClamp.getY(), -2.0E7D, 2.0E7D);
double z = GrimMath.clamp(toClamp.getZ(), -3.0E7D, 3.0E7D);
return clampVector(toClamp.getX(), toClamp.getY(), toClamp.getZ());
}
@Contract("_ -> new")
public static @NotNull Vector3d clampVector(double toClampX, double toClampY, double toClampZ) {
double x = GrimMath.clamp(toClampX, -3.0E7D, 3.0E7D);
double y = GrimMath.clamp(toClampY, -2.0E7D, 2.0E7D);
double z = GrimMath.clamp(toClampZ, -3.0E7D, 3.0E7D);
return new Vector3d(x, y, z);
}
}
@@ -31,7 +31,7 @@ public final class BoundingBoxSize {
return 0.75f;
} else if (EntityTypes.PANDA.equals(type)) {
return 1.3f;
} else if (EntityTypes.BAT.equals(type) || EntityTypes.PARROT.equals(type) || EntityTypes.COD.equals(type) || EntityTypes.EVOKER_FANGS.equals(type) || EntityTypes.TROPICAL_FISH.equals(type) || EntityTypes.FROG.equals(type)) {
} else if (EntityTypes.BAT.equals(type) || EntityTypes.PARROT.equals(type) || EntityTypes.COD.equals(type) || EntityTypes.EVOKER_FANGS.equals(type) || EntityTypes.TROPICAL_FISH.equals(type) || EntityTypes.FROG.equals(type) || EntityTypes.COPPER_GOLEM.equals(type)) {
return 0.5f;
} else if (EntityTypes.ARMADILLO.equals(type) || EntityTypes.BEE.equals(type) || EntityTypes.PUFFERFISH.equals(type) || EntityTypes.SALMON.equals(type) || EntityTypes.SNOW_GOLEM.equals(type) || EntityTypes.CAVE_SPIDER.equals(type)) {
return 0.7f;
@@ -87,7 +87,7 @@ public final class BoundingBoxSize {
return 0.98f;
} else if (EntityTypes.isTypeInstanceOf(type, EntityTypes.MINECART_ABSTRACT)) {
return 0.98f;
} else if (EntityTypes.PLAYER.equals(type)) {
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
return 0.6f;
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
return 1.4f;
@@ -199,7 +199,7 @@ public final class BoundingBoxSize {
return 0.1;
} else if (EntityTypes.EVOKER.equals(type) || EntityTypes.ILLUSIONER.equals(type) || EntityTypes.PILLAGER.equals(type) || EntityTypes.RAVAGER.equals(type) || EntityTypes.VINDICATOR.equals(type) || EntityTypes.WITCH.equals(type)) {
return -0.45;
} else if (EntityTypes.PLAYER.equals(type)) {
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
return -0.35;
}
@@ -337,7 +337,7 @@ public final class BoundingBoxSize {
}
return 1.8f;
} else if (EntityTypes.PLAYER.equals(type)) {
} else if (EntityTypes.PLAYER.equals(type) || EntityTypes.MANNEQUIN.equals(type)) {
return 1.8f;
} else if (EntityTypes.POLAR_BEAR.equals(type)) {
return 1.4f;
@@ -405,6 +405,8 @@ public final class BoundingBoxSize {
return 1.8F;
} else if (EntityTypes.FIREWORK_ROCKET.equals(type)) {
return 0.25F;
} else if (EntityTypes.COPPER_GOLEM.equals(type)) {
return 1.0F;
}
return 1.95f;
}
@@ -15,6 +15,8 @@ import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.Pair;
import ac.grim.grimac.utils.data.VectorData;
import ac.grim.grimac.utils.data.tags.SyncedTags;
import ac.grim.grimac.utils.function.BlockStateAtPosPredicate;
import ac.grim.grimac.utils.function.Int3Consumer;
import ac.grim.grimac.utils.latency.CompensatedWorld;
import ac.grim.grimac.utils.math.GrimMath;
import ac.grim.grimac.utils.math.Location;
@@ -42,6 +44,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@UtilityClass
@@ -145,7 +148,7 @@ public final class Collisions {
for (float stepHeight : stepHeights) {
Vector3dm vec3d2 = collideBoundingBoxLegacy(new Vector3dm(desiredX, stepHeight, desiredZ), startingOffsetBox, stepCollisions, order);
if (getHorizontalDistanceSqr(vec3d2) > getHorizontalDistanceSqr(collisionResult)) {
if (getHorizontalDistanceSqr(vec3d2.getX(), vec3d2.getZ()) > getHorizontalDistanceSqr(collisionResult.getX(), collisionResult.getZ())) {
final double d = player.boundingBox.minY - startingOffsetBox.minY;
collisionResult = vec3d2.add(new Vector3dm(0.0, -d, 0.0));
break;
@@ -159,13 +162,13 @@ public final class Collisions {
Vector3dm stepUpBugFix = collideBoundingBoxLegacy(new Vector3dm(0, stepUpHeight, 0), player.boundingBox.copy().expandToCoordinate(desiredX, 0, desiredZ), desiredMovementCollisionBoxes, order);
if (stepUpBugFix.getY() < stepUpHeight) {
Vector3dm stepUpBugFixResult = collideBoundingBoxLegacy(new Vector3dm(desiredX, 0, desiredZ), player.boundingBox.copy().offset(0, stepUpBugFix.getY(), 0), desiredMovementCollisionBoxes, order).add(stepUpBugFix);
if (getHorizontalDistanceSqr(stepUpBugFixResult) > getHorizontalDistanceSqr(regularStepUp)) {
if (getHorizontalDistanceSqr(stepUpBugFixResult.getX(), stepUpBugFixResult.getZ()) > getHorizontalDistanceSqr(regularStepUp.getX(), regularStepUp.getZ())) {
regularStepUp = stepUpBugFixResult;
}
}
}
if (getHorizontalDistanceSqr(regularStepUp) > getHorizontalDistanceSqr(collisionResult)) {
if (getHorizontalDistanceSqr(regularStepUp.getX(), regularStepUp.getZ()) > getHorizontalDistanceSqr(collisionResult.getX(), collisionResult.getZ())) {
collisionResult = regularStepUp.add(collideBoundingBoxLegacy(new Vector3dm(0, -regularStepUp.getY() + (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? desiredY : 0), 0), player.boundingBox.copy().offset(regularStepUp.getX(), regularStepUp.getY(), regularStepUp.getZ()), desiredMovementCollisionBoxes, order));
}
}
@@ -385,8 +388,8 @@ public final class Collisions {
return !getCollisionBoxes(player, playerBB, null, true);
}
public static double getHorizontalDistanceSqr(Vector3dm vector) {
return vector.getX() * vector.getX() + vector.getZ() * vector.getZ();
public static double getHorizontalDistanceSqr(double x, double z) {
return x * x + z * z;
}
public static Vector3dm maybeBackOffFromEdge(Vector3dm vec3, GrimPlayer player, boolean overrideVersion) {
@@ -517,17 +520,17 @@ public final class Collisions {
if (blockAbove.getType().isAir()) {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
if (block.isDrag()) {
vector.vector.setY(Math.max(-0.9D, vector.vector.getY() - 0.03D));
vector.setY(Math.max(-0.9D, vector.vectorY - 0.03D));
} else {
vector.vector.setY(Math.min(1.8D, vector.vector.getY() + 0.1D));
vector.setY(Math.min(1.8D, vector.vectorY + 0.1D));
}
}
} else {
for (VectorData vector : player.getPossibleVelocitiesMinusKnockback()) {
if (block.isDrag()) {
vector.vector.setY(Math.max(-0.3D, vector.vector.getY() - 0.03D));
vector.setY(Math.max(-0.3D, vector.vectorY - 0.03D));
} else {
vector.vector.setY(Math.min(0.7D, vector.vector.getY() + 0.06D));
vector.setY(Math.min(0.7D, vector.vectorY + 0.06D));
}
}
}
@@ -742,8 +745,13 @@ public final class Collisions {
return box.isFullBlock();
}
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, Predicate<WrappedBlockState> searchingFor) {
return hasMaterial(player, checkBox, (state, x, y, z) -> searchingFor.test(state));
}
// Thanks Tuinity
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, Predicate<Pair<WrappedBlockState, Vector3d>> searchingFor) {
public static boolean hasMaterial(GrimPlayer player, SimpleCollisionBox checkBox, BlockStateAtPosPredicate searchingFor) {
int minBlockX = (int) Math.floor(checkBox.minX);
int maxBlockX = (int) Math.floor(checkBox.maxX);
int minBlockY = (int) Math.floor(checkBox.minY);
@@ -797,7 +805,7 @@ public final class Collisions {
WrappedBlockState data = section.get(CompensatedWorld.blockVersion, x & 0xF, y & 0xF, z & 0xF, false);
if (searchingFor.test(new Pair<>(data, new Vector3d(x, y, z))))
if (searchingFor.test(data, x, y, z))
return true;
}
}
@@ -808,7 +816,7 @@ public final class Collisions {
}
// Thanks Tuinity
public static void forEachCollisionBox(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox checkBox, @NotNull Consumer<@NotNull Vector3d> searchingFor) {
public static void forEachCollisionBox(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox checkBox, @NotNull Int3Consumer searchingFor) {
int minBlockX = (int) Math.floor(checkBox.minX - COLLISION_EPSILON) - 1;
int maxBlockX = (int) Math.floor(checkBox.maxX + COLLISION_EPSILON) + 1;
int minBlockY = (int) Math.floor(checkBox.minY - COLLISION_EPSILON) - 1;
@@ -876,7 +884,7 @@ public final class Collisions {
final CollisionBox collisionBox = CollisionData.getData(type).getMovementCollisionBox(player, player.getClientVersion(), data, x, y, z);
if (collisionBox.isIntersected(checkBox)) {
searchingFor.accept(new Vector3d(x, y, z));
searchingFor.accept(x, y, z);
}
}
}
@@ -8,10 +8,9 @@ import org.jetbrains.annotations.NotNull;
@UtilityClass
public class FluidFallingAdjustedMovement {
public static Vector3dm getFluidFallingAdjustedMovement(@NotNull GrimPlayer player, double gravity, boolean isFalling, Vector3dm velocity) {
if (!player.hasGravity || player.isSprinting) return velocity;
isFalling = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? isFalling : velocity.getY() < 0;
double newY = isFalling && Math.abs(velocity.getY() - 0.005) >= 0.003 && Math.abs(velocity.getY() - gravity / 16.0) < 0.003 ? -0.003 : velocity.getY() - gravity / 16.0;
return new Vector3dm(velocity.getX(), newY, velocity.getZ());
public static double getAdjustedY(GrimPlayer player, double gravity, boolean isFalling, double currentY) {
if (!player.hasGravity || player.isSprinting) return currentY;
isFalling = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_14) ? isFalling : currentY < 0;
return isFalling && Math.abs(currentY - 0.005) >= 0.003 && Math.abs(currentY - gravity / 16.0) < 0.003 ? -0.003 : currentY - gravity / 16.0;
}
}
@@ -63,7 +63,7 @@ public class FluidTypeFlowing {
if ((state.getType() == StateTypes.WATER || state.getType() == StateTypes.LAVA) && state.getLevel() >= 8) {
for (BlockFace enumdirection : new BlockFace[]{BlockFace.NORTH, BlockFace.EAST, BlockFace.SOUTH, BlockFace.WEST}) {
if (isSolidFace(player, originalX, originalY, originalZ, enumdirection) || isSolidFace(player, originalX, originalY + 1, originalZ, enumdirection)) {
vec3d = normalizeVectorWithoutNaN(vec3d).add(new Vector3dm(0.0D, -6.0D, 0.0D));
vec3d = normalizeVectorWithoutNaN(vec3d).add(0.0D, -6.0D, 0.0D);
break;
}
}
@@ -14,7 +14,7 @@ import java.util.OptionalInt;
@UtilityClass
public class JumpPower {
public static void jumpFromGround(@NotNull GrimPlayer player, @NotNull Vector3dm vector) {
public static void jumpFromGround(@NotNull GrimPlayer player, @NotNull double[] vectorArr) {
float jumpPower = getJumpPower(player);
final OptionalInt jumpBoost = player.compensatedEntities.getPotionLevelForPlayer(PotionTypes.JUMP_BOOST);
@@ -22,18 +22,20 @@ public class JumpPower {
jumpPower += 0.1f * (jumpBoost.getAsInt() + 1);
}
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5) && jumpPower <= 1.0E-5f)
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5) && jumpPower <= 1.0E-5f) {
return;
}
vector.setY(player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) ? jumpPower : Math.max(jumpPower, vector.getY()));
// Directly set the Y-value in the array.
vectorArr[1] = player.getClientVersion().isOlderThan(ClientVersion.V_1_21_2) ? jumpPower : Math.max(jumpPower, vectorArr[1]);
if (player.isSprinting) {
float radRotation = GrimMath.radians(player.yaw);
if (player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5)) {
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2, 0.0, player.trigHandler.cos(radRotation) * 0.2));
} else {
vector.add(new Vector3dm(-player.trigHandler.sin(radRotation) * 0.2F, 0.0, player.trigHandler.cos(radRotation) * 0.2F));
}
double sprintFactor = player.getClientVersion().isNewerThanOrEquals(ClientVersion.V_1_20_5) ? 0.2 : 0.2F;
// Add to the X and Z values in the array.
vectorArr[0] += -player.trigHandler.sin(radRotation) * sprintFactor;
vectorArr[2] += player.trigHandler.cos(radRotation) * sprintFactor;
}
}
@@ -3,15 +3,13 @@ package ac.grim.grimac.utils.nmsutil;
import ac.grim.grimac.player.GrimPlayer;
import ac.grim.grimac.utils.collisions.datatypes.SimpleCollisionBox;
import ac.grim.grimac.utils.data.MainSupportingBlockData;
import ac.grim.grimac.utils.math.GrimMath;
import com.github.retrooper.packetevents.util.Vector3d;
import com.github.retrooper.packetevents.util.Vector3i;
import com.google.common.util.concurrent.AtomicDouble;
import lombok.experimental.UtilityClass;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicReference;
@UtilityClass
public class MainSupportingBlockPosFinder {
public MainSupportingBlockData findMainSupportingBlockPos(GrimPlayer player, MainSupportingBlockData lastSupportingBlock, Vector3d lastMovement, SimpleCollisionBox maxPose, boolean isOnGround) {
@@ -34,27 +32,33 @@ public class MainSupportingBlockPosFinder {
return new MainSupportingBlockData(null, true);
}
private static class BestBlockHolder {
Vector3i pos;
double distanceSquared = Double.MAX_VALUE;
}
// Yes we could make this way more efficient with bit packing with 0 object allocations
// No I'm not making this code less readable unless we need to
private @Nullable Vector3i findSupportingBlock(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox searchBox) {
Vector3d playerPos = new Vector3d(player.x, player.y, player.z);
final double playerX = player.x;
final double playerY = player.y;
final double playerZ = player.z;
AtomicReference<Vector3i> bestBlockPos = new AtomicReference<>();
AtomicDouble blockPosDistance = new AtomicDouble(Double.MAX_VALUE);
final BestBlockHolder bestBlock = new BestBlockHolder();
Collisions.forEachCollisionBox(player, searchBox, pos -> {
Vector3i blockPos = pos.toVector3i();
Vector3d blockPosAsVector3d = new Vector3d(blockPos.getX() + 0.5, blockPos.getY() + 0.5, blockPos.getZ() + 0.5);
double distance = playerPos.distanceSquared(blockPosAsVector3d);
Collisions.forEachCollisionBox(player, searchBox, (blockPosX, blockPosY, blockPosZ) -> {
double distanceSquared = GrimMath.distanceSquared(playerX, playerY, playerZ, blockPosX + 0.5, blockPosY + 0.5, blockPosZ + 0.5);
if (distance < blockPosDistance.get() || distance == blockPosDistance.get() && (bestBlockPos.get() == null || firstHasPriorityOverSecond(blockPos, bestBlockPos.get()))) {
bestBlockPos.set(blockPos);
blockPosDistance.set(distance);
if (distanceSquared < bestBlock.distanceSquared || distanceSquared == bestBlock.distanceSquared && (bestBlock.pos == null || firstHasPriorityOverSecond(blockPosX, blockPosY, blockPosZ, bestBlock.pos))) {
bestBlock.pos = new Vector3i(blockPosX, blockPosY, blockPosZ);
bestBlock.distanceSquared = distanceSquared;
}
});
return bestBlockPos.get();
return bestBlock.pos;
}
private boolean firstHasPriorityOverSecond(@NotNull Vector3i first, @NotNull Vector3i second) {
private boolean firstHasPriorityOverSecond(int x, int y, int z, @NotNull Vector3i second) {
// Order of loop is X, Y, and Z
// We prioritize lowest Y axis, then lowest X axis, then lowest Z axis
// Ties among the X and Z positions are broken by the order of looping being X
@@ -67,10 +71,10 @@ public class MainSupportingBlockPosFinder {
// X 0 0
// 0 0 X
// But the upper left would win here because of prioritizing negative X and negative Z
if (first.getY() < second.getY()) return true;
if (y < second.getY()) return true;
double sumX = second.getX() - first.getX();
double sumY = second.getZ() - first.getZ();
double sumX = second.getX() - x;
double sumY = second.getZ() - z;
double horizontalSumTotal = sumX + sumY;
if (horizontalSumTotal == 0) {
@@ -9,56 +9,53 @@ import lombok.Getter;
@Getter
public class Ray implements Cloneable {
private final Vector3dm origin;
private final Vector3dm direction;
public final double originX;
public final double originY;
public final double originZ;
public Ray(Vector3dm origin, Vector3dm direction) {
this.origin = origin;
this.direction = direction;
public final double dirX;
public final double dirY;
public final double dirZ;
public Ray(double originX, double originY, double originZ, double dirX, double dirY, double dirZ) {
this.originX = originX;
this.originY = originY;
this.originZ = originZ;
this.dirX = dirX;
this.dirY = dirY;
this.dirZ = dirZ;
}
public Ray(GrimPlayer player, double x, double y, double z, float xRot, float yRot) {
this.origin = new Vector3dm(x, y, z);
this.direction = calculateDirection(player, xRot, yRot);
}
this.originX = x;
this.originY = y;
this.originZ = z;
// Account for FastMath by using player's trig handler
// Copied from hawk which probably copied it from NMS
public static Vector3dm calculateDirection(GrimPlayer player, float xRot, float yRot) {
Vector3dm vector = new Vector3dm();
// Account for FastMath by using player's trig handler
// Copied from hawk which probably copied it from NMS
float rotX = (float) Math.toRadians(xRot);
float rotY = (float) Math.toRadians(yRot);
vector.setY(-player.trigHandler.sin(rotY));
dirY = -player.trigHandler.sin(rotY);
double xz = player.trigHandler.cos(rotY);
vector.setX(-xz * player.trigHandler.sin(rotX));
vector.setZ(xz * player.trigHandler.cos(rotX));
return vector;
dirX = -xz * player.trigHandler.sin(rotX);
dirZ = xz * player.trigHandler.cos(rotX);
}
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
public Ray clone() {
return new Ray(this.origin.clone(), this.direction.clone());
return new Ray(this.originX, this.originY, this.originZ, this.dirX, this.dirY, this.dirZ);
}
public String toString() {
return "origin: " + origin + " direction: " + direction;
return "origin: " + this.originX + "," + this.originY + "," + this.originZ + " direction: " + this.dirX + "," + this.dirY + "," + this.dirZ;
}
public Vector3dm getPointAtDistance(double distance) {
Vector3dm dir = new Vector3dm(direction.getX(), direction.getY(), direction.getZ());
Vector3dm orig = new Vector3dm(origin.getX(), origin.getY(), origin.getZ());
return orig.add(dir.multiply(distance));
}
// https://en.wikipedia.org/wiki/Skew_lines#Nearest_Points
public Pair<Vector3dm, Vector3dm> closestPointsBetweenLines(Ray other) {
Vector3dm n1 = direction.clone().crossProduct(other.direction.clone().crossProduct(direction));
Vector3dm n2 = other.direction.clone().crossProduct(direction.clone().crossProduct(other.direction));
Vector3dm c1 = origin.clone().add(direction.clone().multiply(other.origin.clone().subtract(origin).dot(n2) / direction.dot(n2)));
Vector3dm c2 = other.origin.clone().add(other.direction.clone().multiply(origin.clone().subtract(other.origin).dot(n1) / other.direction.dot(n1)));
return new Pair<>(c1, c2);
return new Vector3dm(
this.originX + this.dirX * distance,
this.originY + this.dirY * distance,
this.originZ + this.dirZ * distance
);
}
}
@@ -17,13 +17,15 @@ import org.jetbrains.annotations.Nullable;
public class ReachUtils {
// Copied from 1.8... I couldn't figure out 1.14+. "Enterprise" java code is unreadable!
@Contract("_, _, _ -> new")
public static @NotNull Pair<@Nullable Vector3dm, @Nullable BlockFace> calculateIntercept(@NotNull SimpleCollisionBox self, @NotNull Vector3dm origin, @NotNull Vector3dm end) {
Vector3dm minX = getIntermediateWithXValue(origin, end, self.minX);
Vector3dm maxX = getIntermediateWithXValue(origin, end, self.maxX);
Vector3dm minY = getIntermediateWithYValue(origin, end, self.minY);
Vector3dm maxY = getIntermediateWithYValue(origin, end, self.maxY);
Vector3dm minZ = getIntermediateWithZValue(origin, end, self.minZ);
Vector3dm maxZ = getIntermediateWithZValue(origin, end, self.maxZ);
public static @NotNull Pair<@Nullable Vector3dm, @Nullable BlockFace> calculateIntercept(@NotNull SimpleCollisionBox self,
double originX, double originY, double originZ,
double endX, double endY, double endZ) {
Vector3dm minX = getIntermediateWithXValue(originX, originY, originZ, endX, endY, endZ, self.minX);
Vector3dm maxX = getIntermediateWithXValue(originX, originY, originZ, endX, endY, endZ, self.maxX);
Vector3dm minY = getIntermediateWithYValue(originX, originY, originZ, endX, endY, endZ, self.minY);
Vector3dm maxY = getIntermediateWithYValue(originX, originY, originZ, endX, endY, endZ, self.maxY);
Vector3dm minZ = getIntermediateWithZValue(originX, originY, originZ, endX, endY, endZ, self.minZ);
Vector3dm maxZ = getIntermediateWithZValue(originX, originY, originZ, endX, endY, endZ, self.maxZ);
if (!isVecInYZ(self, minX)) minX = null;
if (!isVecInYZ(self, maxX)) maxX = null;
@@ -40,27 +42,27 @@ public class ReachUtils {
bestFace = BlockFace.WEST;
}
if (maxX != null && (best == null || origin.distanceSquared(maxX) < origin.distanceSquared(best))) {
if (maxX != null && (best == null || maxX.distanceSquared(originX, originY, originZ) < best.distanceSquared(originX, originY, originZ))) {
best = maxX;
bestFace = BlockFace.EAST;
}
if (minY != null && (best == null || origin.distanceSquared(minY) < origin.distanceSquared(best))) {
if (minY != null && (best == null || minY.distanceSquared(originX, originY, originZ) < best.distanceSquared(originX, originY, originZ))) {
best = minY;
bestFace = BlockFace.DOWN;
}
if (maxY != null && (best == null || origin.distanceSquared(maxY) < origin.distanceSquared(best))) {
if (maxY != null && (best == null || maxY.distanceSquared(originX, originY, originZ) < best.distanceSquared(originX, originY, originZ))) {
best = maxY;
bestFace = BlockFace.UP;
}
if (minZ != null && (best == null || origin.distanceSquared(minZ) < origin.distanceSquared(best))) {
if (minZ != null && (best == null || minZ.distanceSquared(originX, originY, originZ) < best.distanceSquared(originX, originY, originZ))) {
best = minZ;
bestFace = BlockFace.NORTH;
}
if (maxZ != null && (best == null || origin.distanceSquared(maxZ) < origin.distanceSquared(best))) {
if (maxZ != null && (best == null || maxZ.distanceSquared(originX, originY, originZ) < best.distanceSquared(originX, originY, originZ))) {
best = maxZ;
bestFace = BlockFace.SOUTH;
}
@@ -72,16 +74,20 @@ public class ReachUtils {
* Returns a new vector with x value equal to the second parameter, along the line between this vector and the
* passed in vector, or null if not possible.
*/
public static @Nullable Vector3dm getIntermediateWithXValue(@NotNull Vector3dm self, @NotNull Vector3dm other, double x) {
double deltaX = other.getX() - self.getX();
double deltaY = other.getY() - self.getY();
double deltaZ = other.getZ() - self.getZ();
public static @Nullable Vector3dm getIntermediateWithXValue(
double originX, double originY, double originZ,
double endX, double endY, double endZ,
double targetX
) {
double deltaX = endX - originX;
double deltaY = endY - originY;
double deltaZ = endZ - originZ;
if (deltaX * deltaX < 1.0000000116860974E-7D) {
return null;
} else {
double d3 = (x - self.getX()) / deltaX;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(self.getX() + deltaX * d3, self.getY() + deltaY * d3, self.getZ() + deltaZ * d3) : null;
double d3 = (targetX - originX) / deltaX;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(originX + deltaX * d3, originY + deltaY * d3, originZ + deltaZ * d3) : null;
}
}
@@ -89,16 +95,20 @@ public class ReachUtils {
* Returns a new vector with y value equal to the second parameter, along the line between this vector and the
* passed in vector, or null if not possible.
*/
public static @Nullable Vector3dm getIntermediateWithYValue(@NotNull Vector3dm self, @NotNull Vector3dm other, double y) {
double deltaX = other.getX() - self.getX();
double deltaY = other.getY() - self.getY();
double deltaZ = other.getZ() - self.getZ();
public static @Nullable Vector3dm getIntermediateWithYValue(
double originX, double originY, double originZ,
double endX, double endY, double endZ,
double targetY
) {
double deltaX = endX - originX;
double deltaY = endY - originY;
double deltaZ = endZ - originZ;
if (deltaY * deltaY < 1.0000000116860974E-7D) {
return null;
} else {
double d3 = (y - self.getY()) / deltaY;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(self.getX() + deltaX * d3, self.getY() + deltaY * d3, self.getZ() + deltaZ * d3) : null;
double d3 = (targetY - originY) / deltaY;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(originX + deltaX * d3, originY + deltaY * d3, originZ + deltaZ * d3) : null;
}
}
@@ -106,16 +116,20 @@ public class ReachUtils {
* Returns a new vector with z value equal to the second parameter, along the line between this vector and the
* passed in vector, or null if not possible.
*/
public static @Nullable Vector3dm getIntermediateWithZValue(@NotNull Vector3dm self, @NotNull Vector3dm other, double z) {
double deltaX = other.getX() - self.getX();
double deltaY = other.getY() - self.getY();
double deltaZ = other.getZ() - self.getZ();
public static @Nullable Vector3dm getIntermediateWithZValue(
double originX, double originY, double originZ,
double endX, double endY, double endZ,
double targetZ
) {
double deltaX = endX - originX;
double deltaY = endY - originY;
double deltaZ = endZ - originZ;
if (deltaZ * deltaZ < 1.0000000116860974E-7D) {
return null;
} else {
double d3 = (z - self.getZ()) / deltaZ;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(self.getX() + deltaX * d3, self.getY() + deltaY * d3, self.getZ() + deltaZ * d3) : null;
double d3 = (targetZ - originZ) / deltaZ;
return d3 >= 0.0D && d3 <= 1.0D ? new Vector3dm(originX + deltaX * d3, originY + deltaY * d3, originZ + deltaZ * d3) : null;
}
}
@@ -165,8 +179,8 @@ public class ReachUtils {
}
}
public static boolean isVecInside(@NotNull SimpleCollisionBox self, @NotNull Vector3dm vec) {
return vec.getX() > self.minX && vec.getX() < self.maxX && (vec.getY() > self.minY && vec.getY() < self.maxY && vec.getZ() > self.minZ && vec.getZ() < self.maxZ);
public static boolean isVecInside(@NotNull SimpleCollisionBox self, double x, double y, double z) {
return x > self.minX && x < self.maxX && y > self.minY && y < self.maxY && z > self.minZ && z < self.maxZ;
}
public static double getMinReachToBox(@NotNull GrimPlayer player, @NotNull SimpleCollisionBox targetBox) {
@@ -179,9 +193,8 @@ public class ReachUtils {
if (giveMovementThresholdLenience) targetBox.expand(player.getMovementThreshold());
final double[] possibleEyeHeights = player.getPossibleEyeHeights();
for (double eyes : possibleEyeHeights) {
Vector3dm from = new Vector3dm(player.x, player.y + eyes, player.z);
Vector3dm closestPoint = VectorUtils.cutBoxToVector(from, targetBox);
lowest = Math.min(lowest, closestPoint.distance(from));
Vector3dm closestPoint = VectorUtils.cutBoxToVector(player.x, player.y + eyes, player.z, targetBox);
lowest = Math.min(lowest, closestPoint.distance(player.x, player.y + eyes, player.z));
}
return lowest;
@@ -47,7 +47,8 @@ public class WorldRayTrace {
BlockFace bestFace = null;
for (SimpleCollisionBox box : boxes) {
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() == null) continue; // No intercept
Vector3dm hitLoc = intercept.first();
@@ -69,7 +70,8 @@ public class WorldRayTrace {
: player.compensatedWorld.getFluidLevelAt(vector3i.getX(), vector3i.getY(), vector3i.getZ());
SimpleCollisionBox box = new SimpleCollisionBox(vector3i.getX(), vector3i.getY(), vector3i.getZ(), vector3i.getX() + 1, vector3i.getY() + waterHeight, vector3i.getZ() + 1);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOrigin(), trace.getPointAtDistance(distance));
Vector3dm pointAtDistance = trace.getPointAtDistance(distance);
Pair<Vector3dm, BlockFace> intercept = ReachUtils.calculateIntercept(box, trace.getOriginX(), trace.getOriginY(), trace.getOriginZ(), pointAtDistance.getX(), pointAtDistance.getY(), pointAtDistance.getZ());
if (intercept.first() != null) {
return new HitData(vector3i, intercept.first(), intercept.second(), block);
@@ -52,4 +52,15 @@ public abstract class AbstractFabricGrimEntity implements GrimEntity {
this.entity.getPitch(1.0F)
);
}
@Override
public double distanceSquared(double oX, double oY, double oZ) {
double x = this.entity.getX();
double y = this.entity.getY();
double z = this.entity.getZ();
double distX = (x - oX) * (x - oX);
double distY = (y - oY) * (y - oY);
double distZ = (z - oZ) * (z - oZ);
return distX + distY + distZ;
}
}
@@ -1,6 +1,5 @@
package ac.grim.grimac.platform.fabric.player;
import ac.grim.grimac.GrimAPI;
import ac.grim.grimac.platform.api.entity.GrimEntity;
import ac.grim.grimac.platform.api.player.PlatformInventory;
import ac.grim.grimac.platform.api.player.PlatformPlayer;
@@ -18,7 +17,6 @@ import net.minecraft.server.network.ServerPlayerEntity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.UUID;
public abstract class AbstractFabricPlatformPlayer extends AbstractFabricGrimEntity implements PlatformPlayer {