mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
Migrate to SERVER-AUTHORITATIVE MOVEMENT dun dun dunnnn
This commit is contained in:
parent
52ce17dee6
commit
e17ad64d8c
13 changed files with 557 additions and 340 deletions
|
@ -27,6 +27,7 @@ package org.geysermc.geyser.level.physics;
|
|||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.util.TriState;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.GenericMath;
|
||||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
|
@ -153,11 +154,10 @@ public class CollisionManager {
|
|||
* the two versions. Will also send corrected movement packets back to Bedrock if they collide with pistons.
|
||||
*
|
||||
* @param bedrockPosition the current Bedrock position of the client
|
||||
* @param onGround whether the Bedrock player is on the ground
|
||||
* @param teleported whether the Bedrock player has teleported to a new position. If true, movement correction is skipped.
|
||||
* @return the position to send to the Java server, or null to cancel sending the packet
|
||||
*/
|
||||
public @Nullable Vector3d adjustBedrockPosition(Vector3f bedrockPosition, boolean onGround, boolean teleported) {
|
||||
public @Nullable CollisionResult adjustBedrockPosition(Vector3f bedrockPosition, boolean teleported) {
|
||||
PistonCache pistonCache = session.getPistonCache();
|
||||
// Bedrock clients tend to fall off of honey blocks, so we need to teleport them to the new position
|
||||
if (pistonCache.isPlayerAttachedToHoney()) {
|
||||
|
@ -176,7 +176,7 @@ public class CollisionManager {
|
|||
playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2);
|
||||
playerBoundingBox.setMiddleZ(position.getZ());
|
||||
|
||||
return playerBoundingBox.getBottomCenter();
|
||||
return new CollisionResult(playerBoundingBox.getBottomCenter(), TriState.NOT_SET);
|
||||
}
|
||||
|
||||
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
||||
|
@ -198,9 +198,9 @@ public class CollisionManager {
|
|||
|
||||
position = playerBoundingBox.getBottomCenter();
|
||||
|
||||
boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0 || onGround;
|
||||
boolean newOnGround = adjustedMovement.getY() != movement.getY() && movement.getY() < 0;
|
||||
// Send corrected position to Bedrock if they differ by too much to prevent de-syncs
|
||||
if (onGround != newOnGround || movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
|
||||
if (/*onGround != newOnGround || */movement.distanceSquared(adjustedMovement) > INCORRECT_MOVEMENT_THRESHOLD) {
|
||||
PlayerEntity playerEntity = session.getPlayerEntity();
|
||||
// Client will dismount if on a vehicle
|
||||
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
||||
|
@ -208,12 +208,12 @@ public class CollisionManager {
|
|||
}
|
||||
}
|
||||
|
||||
if (!onGround) {
|
||||
if (!newOnGround) {
|
||||
// Trim the position to prevent rounding errors that make Java think we are clipping into a block
|
||||
position = Vector3d.from(position.getX(), Double.parseDouble(DECIMAL_FORMAT.format(position.getY())), position.getZ());
|
||||
}
|
||||
|
||||
return position;
|
||||
return new CollisionResult(position, TriState.byBoolean(newOnGround));
|
||||
}
|
||||
|
||||
// TODO: This makes the player look upwards for some reason, rotation values must be wrong
|
||||
|
|
|
@ -25,10 +25,11 @@
|
|||
|
||||
package org.geysermc.geyser.level.physics;
|
||||
|
||||
import net.kyori.adventure.util.TriState;
|
||||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
|
||||
/**
|
||||
* Holds the result of a collision check.
|
||||
*/
|
||||
public record CollisionResult(Vector3d correctedMovement, boolean horizontalCollision) {
|
||||
public record CollisionResult(Vector3d correctedMovement, TriState onGround) {
|
||||
}
|
||||
|
|
|
@ -71,7 +71,6 @@ import org.cloudburstmc.protocol.bedrock.packet.MultiplayerSettingsPacket;
|
|||
import org.cloudburstmc.protocol.bedrock.packet.NpcRequestPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PhotoInfoRequestPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PhotoTransferPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerHotbarPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PurchaseReceiptPacket;
|
||||
|
@ -318,7 +317,7 @@ class CodecProcessor {
|
|||
.updateSerializer(ClientCheatAbilityPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(CraftingEventPacket.class, ILLEGAL_SERIALIZER)
|
||||
// Illegal unusued serverbound packets that relate to unused features
|
||||
.updateSerializer(PlayerAuthInputPacket.class, ILLEGAL_SERIALIZER)
|
||||
//.updateSerializer(PlayerAuthInputPacket.class, ILLEGAL_SERIALIZER) TODO keeping until we determine which packets should replace
|
||||
.updateSerializer(ClientCacheBlobStatusPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(SubClientLoginPacket.class, ILLEGAL_SERIALIZER)
|
||||
.updateSerializer(SubChunkRequestPacket.class, ILLEGAL_SERIALIZER)
|
||||
|
|
|
@ -290,7 +290,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PacketSignal handle(MovePlayerPacket packet) {
|
||||
public PacketSignal handle(MovePlayerPacket packet) { // TODO
|
||||
if (session.isLoggingIn()) {
|
||||
SetTitlePacket titlePacket = new SetTitlePacket();
|
||||
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
|
||||
|
|
|
@ -161,6 +161,7 @@ import org.geysermc.geyser.session.cache.ChunkCache;
|
|||
import org.geysermc.geyser.session.cache.EntityCache;
|
||||
import org.geysermc.geyser.session.cache.EntityEffectCache;
|
||||
import org.geysermc.geyser.session.cache.FormCache;
|
||||
import org.geysermc.geyser.session.cache.InputCache;
|
||||
import org.geysermc.geyser.session.cache.LodestoneCache;
|
||||
import org.geysermc.geyser.session.cache.PistonCache;
|
||||
import org.geysermc.geyser.session.cache.PreferencesCache;
|
||||
|
@ -210,7 +211,6 @@ import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.Serverbound
|
|||
import org.geysermc.mcprotocollib.protocol.packet.handshake.serverbound.ClientIntentionPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatCommandSignedPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
|
||||
|
@ -276,6 +276,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
private final EntityCache entityCache;
|
||||
private final EntityEffectCache effectCache;
|
||||
private final FormCache formCache;
|
||||
private final InputCache inputCache;
|
||||
private final LodestoneCache lodestoneCache;
|
||||
private final PistonCache pistonCache;
|
||||
private final PreferencesCache preferencesCache;
|
||||
|
@ -523,12 +524,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
@Setter
|
||||
private boolean placedBucket;
|
||||
|
||||
/**
|
||||
* Used to send a movement packet every three seconds if the player hasn't moved. Prevents timeouts when AFK in certain instances.
|
||||
*/
|
||||
@Setter
|
||||
private long lastMovementTimestamp = System.currentTimeMillis();
|
||||
|
||||
/**
|
||||
* Used to send a ServerboundMoveVehiclePacket for every PlayerInputPacket after idling on a boat/horse for more than 100ms
|
||||
*/
|
||||
|
@ -672,6 +667,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
this.entityCache = new EntityCache(this);
|
||||
this.effectCache = new EntityEffectCache();
|
||||
this.formCache = new FormCache(this);
|
||||
this.inputCache = new InputCache(this);
|
||||
this.lodestoneCache = new LodestoneCache();
|
||||
this.pistonCache = new PistonCache(this);
|
||||
this.preferencesCache = new PreferencesCache(this);
|
||||
|
@ -1266,18 +1262,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
protected void tick() {
|
||||
try {
|
||||
pistonCache.tick();
|
||||
// Check to see if the player's position needs updating - a position update should be sent once every 3 seconds
|
||||
if (spawned && (System.currentTimeMillis() - lastMovementTimestamp) > 3000) {
|
||||
// Recalculate in case something else changed position
|
||||
Vector3d position = collisionManager.adjustBedrockPosition(playerEntity.getPosition(), playerEntity.isOnGround(), false);
|
||||
// A null return value cancels the packet
|
||||
if (position != null) {
|
||||
ServerboundMovePlayerPosPacket packet = new ServerboundMovePlayerPosPacket(playerEntity.isOnGround(), false, //FIXME
|
||||
position.getX(), position.getY(), position.getZ());
|
||||
sendDownstreamGamePacket(packet);
|
||||
}
|
||||
lastMovementTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (worldBorder.isResizing()) {
|
||||
worldBorder.resize();
|
||||
|
@ -1668,7 +1652,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
|
||||
startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE);
|
||||
|
||||
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
|
||||
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.SERVER);
|
||||
startGamePacket.setRewindHistorySize(0);
|
||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||
|
||||
|
|
80
core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java
vendored
Normal file
80
core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java
vendored
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.session.cache;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundPlayerInputPacket;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public final class InputCache {
|
||||
private final GeyserSession session;
|
||||
private ServerboundPlayerInputPacket inputPacket = new ServerboundPlayerInputPacket(false, false, false, false, false, false, false);
|
||||
private boolean lastHorizontalCollision;
|
||||
private int ticksSinceLastMovePacket;
|
||||
|
||||
public InputCache(GeyserSession session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void processInputs(PlayerAuthInputPacket packet) {
|
||||
// Input is sent to the server before packet positions, as of 1.21.2
|
||||
Set<PlayerAuthInputData> bedrockInput = packet.getInputData();
|
||||
var oldInputPacket = this.inputPacket;
|
||||
// TODO when is UP_LEFT, etc. used?
|
||||
this.inputPacket = this.inputPacket
|
||||
.withForward(bedrockInput.contains(PlayerAuthInputData.UP))
|
||||
.withBackward(bedrockInput.contains(PlayerAuthInputData.DOWN))
|
||||
.withLeft(bedrockInput.contains(PlayerAuthInputData.LEFT))
|
||||
.withRight(bedrockInput.contains(PlayerAuthInputData.RIGHT))
|
||||
.withJump(bedrockInput.contains(PlayerAuthInputData.JUMPING)) // Looks like this only triggers when the JUMP key input is being pressed. There's also JUMP_DOWN?
|
||||
.withShift(bedrockInput.contains(PlayerAuthInputData.SNEAKING))
|
||||
.withSprint(bedrockInput.contains(PlayerAuthInputData.SPRINTING)); // SPRINTING will trigger even if the player isn't moving
|
||||
|
||||
if (oldInputPacket != this.inputPacket) { // Simple equality check is fine since we're checking for an instance change.
|
||||
session.sendDownstreamGamePacket(this.inputPacket);
|
||||
}
|
||||
}
|
||||
|
||||
public void markPositionPacketSent() {
|
||||
this.ticksSinceLastMovePacket = 0;
|
||||
}
|
||||
|
||||
public boolean shouldSendPositionReminder() {
|
||||
// NOTE: if we implement spectating entities, DO NOT TICK THIS LOGIC THEN.
|
||||
return ++this.ticksSinceLastMovePacket >= 20;
|
||||
}
|
||||
|
||||
public boolean lastHorizontalCollision() {
|
||||
return lastHorizontalCollision;
|
||||
}
|
||||
|
||||
public void setLastHorizontalCollision(boolean lastHorizontalCollision) {
|
||||
this.lastHorizontalCollision = lastHorizontalCollision;
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
|||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
|
@ -42,7 +41,6 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.InventoryTra
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetItemSlotData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
|
@ -187,7 +185,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
default -> false;
|
||||
};
|
||||
if (isGodBridging) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +205,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, belowBlockPos);
|
||||
BlockDefinition extendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
|
||||
if (extendedCollisionDefinition != null && (System.currentTimeMillis() - session.getLastInteractionTime()) < 200) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +225,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
}
|
||||
|
||||
if (isIncorrectHeldItem(session, packet)) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -247,7 +245,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
*/
|
||||
// Blocks cannot be placed or destroyed outside of the world border
|
||||
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -256,7 +254,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
||||
|
||||
if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -270,7 +268,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
double clickDistanceY = clickPositionFullY - blockCenter.getY();
|
||||
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
|
||||
if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) {
|
||||
restoreCorrectBlock(session, blockPos, packet);
|
||||
restoreCorrectBlock(session, blockPos);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -424,53 +422,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
}
|
||||
}
|
||||
}
|
||||
case 2 -> {
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
session.getGeyser().getWorldManager().getBlockAt(session, packet.getBlockPosition()) : session.getBreakingBlock();
|
||||
|
||||
session.setLastBlockPlaced(null);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
|
||||
// Same deal with vanilla block placing as above.
|
||||
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
||||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
||||
|
||||
if (!canInteractWithBlock(session, playerPosition, packet.getBlockPosition())) {
|
||||
restoreCorrectBlock(session, packet.getBlockPosition(), packet);
|
||||
return;
|
||||
}
|
||||
|
||||
int sequence = session.getWorldCache().nextPredictionSequence();
|
||||
session.getWorldCache().markPositionInSequence(packet.getBlockPosition());
|
||||
// -1 means we don't know what block they're breaking
|
||||
if (blockState == -1) {
|
||||
blockState = Block.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
||||
blockBreakPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
|
||||
blockBreakPacket.setPosition(packet.getBlockPosition().toFloat());
|
||||
blockBreakPacket.setData(session.getBlockMappings().getBedrockBlockId(blockState));
|
||||
session.sendUpstreamPacket(blockBreakPacket);
|
||||
session.setBreakingBlock(-1);
|
||||
|
||||
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
|
||||
if (itemFrameEntity != null) {
|
||||
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
|
||||
InteractAction.ATTACK, session.isSneaking());
|
||||
session.sendDownstreamGamePacket(attackPacket);
|
||||
break;
|
||||
}
|
||||
|
||||
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
|
||||
ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket(action, packet.getBlockPosition(), Direction.VALUES[packet.getBlockFace()], sequence);
|
||||
session.sendDownstreamGamePacket(breakPacket);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ITEM_RELEASE:
|
||||
|
@ -550,7 +501,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
}
|
||||
}
|
||||
|
||||
private boolean canInteractWithBlock(GeyserSession session, Vector3f playerPosition, Vector3i packetBlockPosition) {
|
||||
public static boolean canInteractWithBlock(GeyserSession session, Vector3f playerPosition, Vector3i packetBlockPosition) {
|
||||
// ViaVersion sends this 1.20.5+ attribute also, so older servers will have correct range checks.
|
||||
double blockInteractionRange = session.getPlayerEntity().getBlockInteractionRange();
|
||||
|
||||
|
@ -578,7 +529,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
* @param session the session of the Bedrock client
|
||||
* @param blockPos the block position to restore
|
||||
*/
|
||||
private void restoreCorrectBlock(GeyserSession session, Vector3i blockPos, InventoryTransactionPacket packet) {
|
||||
public static void restoreCorrectBlock(GeyserSession session, Vector3i blockPos) {
|
||||
BlockState javaBlockState = session.getGeyser().getWorldManager().blockAt(session, blockPos);
|
||||
BlockDefinition bedrockBlock = session.getBlockMappings().getBedrockBlock(javaBlockState);
|
||||
|
||||
|
@ -605,7 +556,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
session.sendUpstreamPacket(updateWaterPacket);
|
||||
|
||||
// Reset the item in hand to prevent "missing" blocks
|
||||
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot()));
|
||||
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), session.getPlayerInventory().getHeldItemSlot()); // TODO test
|
||||
}
|
||||
|
||||
private boolean isIncorrectHeldItem(GeyserSession session, InventoryTransactionPacket packet) {
|
||||
|
@ -699,9 +650,11 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||
float pitch = (float) -Math.toDegrees(Math.atan2(yDiff, xzHypot));
|
||||
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
ServerboundMovePlayerPosRotPacket returnPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), false, playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), entity.getYaw(), entity.getPitch());
|
||||
ServerboundMovePlayerPosRotPacket returnPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
|
||||
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), entity.getYaw(), entity.getPitch());
|
||||
// This matches Java edition behavior
|
||||
ServerboundMovePlayerPosRotPacket movementPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), false, playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), yaw, pitch);
|
||||
ServerboundMovePlayerPosRotPacket movementPacket = new ServerboundMovePlayerPosRotPacket(entity.isOnGround(), session.getInputCache().lastHorizontalCollision(),
|
||||
playerPosition.getX(), playerPosition.getY(), playerPosition.getZ(), yaw, pitch);
|
||||
session.sendDownstreamGamePacket(movementPacket);
|
||||
|
||||
if (session.getLookBackScheduledFuture() != null) {
|
||||
|
|
|
@ -29,14 +29,11 @@ import org.cloudburstmc.math.vector.Vector3f;
|
|||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerBlockActionData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
|
@ -44,7 +41,6 @@ import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
|||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
|
@ -52,8 +48,6 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
|||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.SkullCache;
|
||||
import org.geysermc.geyser.translator.item.CustomItemTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.BlockUtils;
|
||||
import org.geysermc.geyser.util.CooldownUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
|
@ -61,107 +55,36 @@ import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundSwingPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
||||
|
||||
@Translator(packet = PlayerActionPacket.class)
|
||||
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
|
||||
import java.util.List;
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, PlayerActionPacket packet) {
|
||||
final class BedrockBlockActions {
|
||||
|
||||
static void translate(GeyserSession session, List<PlayerBlockActionData> playerActions) {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
|
||||
// Send book update before any player action
|
||||
if (packet.getAction() != PlayerActionType.RESPAWN) {
|
||||
session.getBookEditCache().checkForSend();
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
for (PlayerBlockActionData blockActionData : playerActions) {
|
||||
handle(session, entity, blockActionData);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3i vector = packet.getBlockPosition();
|
||||
private static void handle(GeyserSession session, SessionPlayerEntity entity, PlayerBlockActionData blockActionData) {
|
||||
PlayerActionType action = blockActionData.getAction();
|
||||
Vector3i vector = blockActionData.getBlockPosition();
|
||||
int blockFace = blockActionData.getFace();
|
||||
|
||||
switch (packet.getAction()) {
|
||||
case RESPAWN -> {
|
||||
// Respawn process is finished and the server and client are both OK with respawning.
|
||||
EntityEventPacket eventPacket = new EntityEventPacket();
|
||||
eventPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
eventPacket.setType(EntityEventType.RESPAWN);
|
||||
eventPacket.setData(0);
|
||||
session.sendUpstreamPacket(eventPacket);
|
||||
// Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
|
||||
// Bounding box must be sent after a player dies and respawns since 1.19.40
|
||||
entity.updateBoundingBox();
|
||||
|
||||
// Needed here since 1.19.81 for dimension switching
|
||||
session.getEntityCache().updateBossBars();
|
||||
}
|
||||
case START_SWIMMING -> {
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
ServerboundPlayerCommandPacket startSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||
session.sendDownstreamGamePacket(startSwimPacket);
|
||||
|
||||
session.setSwimming(true);
|
||||
}
|
||||
}
|
||||
case STOP_SWIMMING -> {
|
||||
// Prevent packet spam when Bedrock players are crawling near the edge of a block
|
||||
if (!session.getCollisionManager().mustPlayerCrawlHere()) {
|
||||
ServerboundPlayerCommandPacket stopSwimPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
||||
session.sendDownstreamGamePacket(stopSwimPacket);
|
||||
|
||||
session.setSwimming(false);
|
||||
}
|
||||
}
|
||||
case START_GLIDE -> {
|
||||
// Otherwise gliding will not work in creative
|
||||
ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
|
||||
session.sendDownstreamGamePacket(playerAbilitiesPacket);
|
||||
sendPlayerGlideToggle(session, entity);
|
||||
}
|
||||
case STOP_GLIDE -> sendPlayerGlideToggle(session, entity);
|
||||
case START_SNEAK -> {
|
||||
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
|
||||
session.sendDownstreamGamePacket(startSneakPacket);
|
||||
|
||||
session.startSneaking();
|
||||
}
|
||||
case STOP_SNEAK -> {
|
||||
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
|
||||
session.sendDownstreamGamePacket(stopSneakPacket);
|
||||
|
||||
session.stopSneaking();
|
||||
}
|
||||
case START_SPRINT -> {
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||
session.sendDownstreamGamePacket(startSprintPacket);
|
||||
session.setSprinting(true);
|
||||
}
|
||||
}
|
||||
case STOP_SPRINT -> {
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
||||
session.sendDownstreamGamePacket(stopSprintPacket);
|
||||
}
|
||||
session.setSprinting(false);
|
||||
}
|
||||
switch (action) {
|
||||
case DROP_ITEM -> {
|
||||
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
|
||||
vector, Direction.VALUES[packet.getFace()], 0);
|
||||
vector, Direction.VALUES[blockFace], 0);
|
||||
session.sendDownstreamGamePacket(dropItemPacket);
|
||||
}
|
||||
case STOP_SLEEP -> {
|
||||
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
|
||||
session.sendDownstreamGamePacket(stopSleepingPacket);
|
||||
}
|
||||
case START_BREAK -> {
|
||||
// Ignore START_BREAK when the player is CREATIVE to avoid Spigot receiving 2 packets it interpets as block breaking. https://github.com/GeyserMC/Geyser/issues/4021
|
||||
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||
|
@ -191,9 +114,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
session.sendUpstreamPacket(startBreak);
|
||||
|
||||
// Account for fire - the client likes to hit the block behind.
|
||||
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, packet.getFace());
|
||||
Vector3i fireBlockPos = BlockUtils.getBlockPosition(vector, blockFace);
|
||||
Block block = session.getGeyser().getWorldManager().blockAt(session, fireBlockPos).block();
|
||||
Direction direction = Direction.VALUES[packet.getFace()];
|
||||
Direction direction = Direction.VALUES[blockFace];
|
||||
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
|
||||
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
|
||||
direction, session.getWorldCache().nextPredictionSequence());
|
||||
|
@ -218,7 +141,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
Vector3f vectorFloat = vector.toFloat();
|
||||
|
||||
BlockState breakingBlockState = BlockState.of(breakingBlock);
|
||||
Direction direction = Direction.VALUES[packet.getFace()];
|
||||
Direction direction = Direction.VALUES[blockFace];
|
||||
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
|
||||
|
||||
double breakTime = BlockUtils.getSessionBreakTime(session, breakingBlockState.block()) * 20;
|
||||
|
@ -304,69 +227,10 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
session.sendUpstreamPacket(animatePacket);
|
||||
}
|
||||
}
|
||||
case START_FLYING -> { // Since 1.20.30
|
||||
if (session.isCanFly()) {
|
||||
if (session.getGameMode() == GameMode.SPECTATOR) {
|
||||
// should already be flying
|
||||
session.sendAdventureSettings();
|
||||
break;
|
||||
}
|
||||
|
||||
if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) {
|
||||
// As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling
|
||||
// If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE
|
||||
session.sendAdventureSettings();
|
||||
break;
|
||||
}
|
||||
|
||||
session.setFlying(true);
|
||||
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true));
|
||||
} else {
|
||||
// update whether we can fly
|
||||
session.sendAdventureSettings();
|
||||
// stop flying
|
||||
PlayerActionPacket stopFlyingPacket = new PlayerActionPacket();
|
||||
stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING);
|
||||
stopFlyingPacket.setBlockPosition(Vector3i.ZERO);
|
||||
stopFlyingPacket.setResultPosition(Vector3i.ZERO);
|
||||
stopFlyingPacket.setFace(0);
|
||||
session.sendUpstreamPacket(stopFlyingPacket);
|
||||
}
|
||||
}
|
||||
case STOP_FLYING -> {
|
||||
session.setFlying(false);
|
||||
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false));
|
||||
}
|
||||
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
|
||||
|
||||
if (state.getValue(Properties.HAS_BOOK, false)) {
|
||||
session.setDroppingLecternBook(true);
|
||||
|
||||
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
|
||||
vector,
|
||||
Direction.DOWN,
|
||||
Hand.MAIN_HAND,
|
||||
0, 0, 0,
|
||||
false,
|
||||
false,
|
||||
session.getWorldCache().nextPredictionSequence());
|
||||
session.sendDownstreamGamePacket(blockPacket);
|
||||
break;
|
||||
}
|
||||
|
||||
Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, packet.getBlockPosition());
|
||||
if (itemFrame != null) {
|
||||
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
|
||||
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
|
||||
session.sendDownstreamGamePacket(interactPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void spawnBlockBreakParticles(GeyserSession session, Direction direction, Vector3i position, BlockState blockState) {
|
||||
private static void spawnBlockBreakParticles(GeyserSession session, Direction direction, Vector3i position, BlockState blockState) {
|
||||
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||
switch (direction) {
|
||||
case UP -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP);
|
||||
|
@ -380,9 +244,4 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||
levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId());
|
||||
session.sendUpstreamPacket(levelEventPacket);
|
||||
}
|
||||
|
||||
private void sendPlayerGlideToggle(GeyserSession session, Entity entity) {
|
||||
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
|
||||
session.sendDownstreamGamePacket(glidePacket);
|
||||
}
|
||||
}
|
|
@ -27,34 +27,33 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
|||
|
||||
import org.cloudburstmc.math.vector.Vector3d;
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.MovePlayerPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||
import org.geysermc.geyser.level.physics.CollisionResult;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.mcprotocollib.network.packet.Packet;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosRotPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerStatusOnlyPacket;
|
||||
|
||||
@Translator(packet = MovePlayerPacket.class)
|
||||
public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, MovePlayerPacket packet) {
|
||||
public final class BedrockMovePlayerTranslator {
|
||||
|
||||
static void translate(GeyserSession session, PlayerAuthInputPacket packet) {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
if (!session.isSpawned()) return;
|
||||
|
||||
session.setLastMovementTimestamp(System.currentTimeMillis());
|
||||
|
||||
// Send book update before the player moves
|
||||
session.getBookEditCache().checkForSend();
|
||||
|
||||
boolean actualPositionChanged = !entity.getPosition().equals(packet.getPosition());
|
||||
// Ignore movement packets until Bedrock's position matches the teleported position
|
||||
if (session.getUnconfirmedTeleport() != null) {
|
||||
if (session.getUnconfirmedTeleport() != null && actualPositionChanged) {
|
||||
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
||||
return;
|
||||
}
|
||||
|
@ -70,7 +69,8 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
float pitch = packet.getRotation().getX();
|
||||
float headYaw = packet.getRotation().getY();
|
||||
|
||||
boolean positionChanged = !entity.getPosition().equals(packet.getPosition());
|
||||
// shouldSendPositionReminder also increments a tick counter, so make sure it's always called.
|
||||
boolean positionChanged = session.getInputCache().shouldSendPositionReminder() || actualPositionChanged;
|
||||
boolean rotationChanged = entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw;
|
||||
|
||||
if (session.getLookBackScheduledFuture() != null) {
|
||||
|
@ -80,19 +80,22 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
session.setLookBackScheduledFuture(null);
|
||||
}
|
||||
|
||||
// This takes into account no movement sent from the client, but the player is trying to move anyway.
|
||||
// (Press into a wall in a corner - you're trying to move but nothing actually happens)
|
||||
boolean horizontalCollision = packet.getInputData().contains(PlayerAuthInputData.HORIZONTAL_COLLISION);
|
||||
|
||||
// If only the pitch and yaw changed
|
||||
// This isn't needed, but it makes the packets closer to vanilla
|
||||
// It also means you can't "lag back" while only looking, in theory
|
||||
if (!positionChanged && rotationChanged) {
|
||||
ServerboundMovePlayerRotPacket playerRotationPacket = new ServerboundMovePlayerRotPacket(packet.isOnGround(), false, yaw, pitch);
|
||||
ServerboundMovePlayerRotPacket playerRotationPacket = new ServerboundMovePlayerRotPacket(entity.isOnGround(), horizontalCollision, yaw, pitch);
|
||||
|
||||
entity.setYaw(yaw);
|
||||
entity.setPitch(pitch);
|
||||
entity.setHeadYaw(headYaw);
|
||||
entity.setOnGround(packet.isOnGround());
|
||||
|
||||
session.sendDownstreamGamePacket(playerRotationPacket);
|
||||
} else {
|
||||
} else if (positionChanged) {
|
||||
// World border collision will be handled by client vehicle
|
||||
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
|
||||
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||
|
@ -100,9 +103,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
}
|
||||
|
||||
if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
|
||||
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
|
||||
if (position != null) { // A null return value cancels the packet
|
||||
boolean onGround = packet.isOnGround();
|
||||
CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT));
|
||||
if (result != null) { // A null return value cancels the packet
|
||||
Vector3d position = result.correctedMovement();
|
||||
boolean onGround = result.onGround().toBooleanOrElse(entity.isOnGround());
|
||||
boolean isBelowVoid = entity.isVoidPositionDesynched();
|
||||
|
||||
boolean teleportThroughVoidFloor, mustResyncPosition;
|
||||
|
@ -138,7 +142,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
// Send rotation updates as well
|
||||
movePacket = new ServerboundMovePlayerPosRotPacket(
|
||||
onGround,
|
||||
false,
|
||||
horizontalCollision,
|
||||
position.getX(), yPosition, position.getZ(),
|
||||
yaw, pitch
|
||||
);
|
||||
|
@ -147,7 +151,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
entity.setHeadYaw(headYaw);
|
||||
} else {
|
||||
// Rotation did not change; don't send an update with rotation
|
||||
movePacket = new ServerboundMovePlayerPosPacket(onGround, false, position.getX(), yPosition, position.getZ());
|
||||
movePacket = new ServerboundMovePlayerPosPacket(onGround, horizontalCollision, position.getX(), yPosition, position.getZ());
|
||||
}
|
||||
|
||||
entity.setPositionManual(packet.getPosition());
|
||||
|
@ -162,6 +166,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
entity.teleportVoidFloorFix(true);
|
||||
}
|
||||
|
||||
session.getInputCache().markPositionPacketSent();
|
||||
session.getSkullCache().updateVisibleSkulls();
|
||||
}
|
||||
} else {
|
||||
|
@ -169,8 +174,12 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
session.getGeyser().getLogger().debug("Recalculating position...");
|
||||
session.getCollisionManager().recalculatePosition();
|
||||
}
|
||||
} else if (horizontalCollision != session.getInputCache().lastHorizontalCollision()) {
|
||||
session.sendDownstreamGamePacket(new ServerboundMovePlayerStatusOnlyPacket(entity.isOnGround(), horizontalCollision));
|
||||
}
|
||||
|
||||
session.getInputCache().setLastHorizontalCollision(horizontalCollision);
|
||||
|
||||
// Move parrots to match if applicable
|
||||
if (entity.getLeftParrot() != null) {
|
||||
entity.getLeftParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false);
|
||||
|
@ -180,11 +189,11 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isInvalidNumber(float val) {
|
||||
private static boolean isInvalidNumber(float val) {
|
||||
return Float.isNaN(val) || Float.isInfinite(val);
|
||||
}
|
||||
|
||||
private boolean isValidMove(GeyserSession session, Vector3f currentPosition, Vector3f newPosition) {
|
||||
private static boolean isValidMove(GeyserSession session, Vector3f currentPosition, Vector3f newPosition) {
|
||||
if (isInvalidNumber(newPosition.getX()) || isInvalidNumber(newPosition.getY()) || isInvalidNumber(newPosition.getZ())) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityEventType;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
||||
|
||||
@Translator(packet = PlayerActionPacket.class)
|
||||
public class BedrockPlayerActionTranslator extends PacketTranslator<PlayerActionPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, PlayerActionPacket packet) {
|
||||
// This packet was used more before server auth movement was needed, but it's still used for a couple things...
|
||||
switch (packet.getAction()) {
|
||||
case RESPAWN -> {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
// Respawn process is finished and the server and client are both OK with respawning.
|
||||
EntityEventPacket eventPacket = new EntityEventPacket();
|
||||
eventPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
eventPacket.setType(EntityEventType.RESPAWN);
|
||||
eventPacket.setData(0);
|
||||
session.sendUpstreamPacket(eventPacket);
|
||||
// Resend attributes or else in rare cases the user can think they're not dead when they are, upon joining the server
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
attributesPacket.setRuntimeEntityId(entity.getGeyserId());
|
||||
attributesPacket.getAttributes().addAll(entity.getAttributes().values());
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
|
||||
// Bounding box must be sent after a player dies and respawns since 1.19.40
|
||||
entity.updateBoundingBox();
|
||||
|
||||
// Needed here since 1.19.81 for dimension switching
|
||||
session.getEntityCache().updateBossBars();
|
||||
}
|
||||
case STOP_SLEEP -> {
|
||||
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(session.getPlayerEntity().getEntityId(), PlayerState.LEAVE_BED);
|
||||
session.sendDownstreamGamePacket(stopSleepingPacket);
|
||||
}
|
||||
case DIMENSION_CHANGE_REQUEST_OR_CREATIVE_DESTROY_BLOCK -> { // Used by client to get book from lecterns and items from item frame in creative mode since 1.20.70
|
||||
Vector3i vector = packet.getBlockPosition();
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, vector);
|
||||
|
||||
if (state.getValue(Properties.HAS_BOOK, false)) {
|
||||
session.setDroppingLecternBook(true);
|
||||
|
||||
ServerboundUseItemOnPacket blockPacket = new ServerboundUseItemOnPacket(
|
||||
vector,
|
||||
Direction.DOWN,
|
||||
Hand.MAIN_HAND,
|
||||
0, 0, 0,
|
||||
false,
|
||||
false,
|
||||
session.getWorldCache().nextPredictionSequence());
|
||||
session.sendDownstreamGamePacket(blockPacket);
|
||||
break;
|
||||
}
|
||||
|
||||
Entity itemFrame = ItemFrameEntity.getItemFrameEntity(session, vector);
|
||||
if (itemFrame != null) {
|
||||
ServerboundInteractPacket interactPacket = new ServerboundInteractPacket(itemFrame.getEntityId(),
|
||||
InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking());
|
||||
session.sendDownstreamGamePacket(interactPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
|
||||
|
||||
import org.cloudburstmc.math.vector.Vector3f;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.ItemUseTransaction;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.ItemFrameEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.level.block.type.Block;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.protocol.bedrock.BedrockInventoryTransactionTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.PlayerState;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundInteractPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerAbilitiesPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerCommandPacket;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Translator(packet = PlayerAuthInputPacket.class)
|
||||
public class BedrockPlayerAuthInputTranslator extends PacketTranslator<PlayerAuthInputPacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, PlayerAuthInputPacket packet) {
|
||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||
session.getInputCache().processInputs(packet);
|
||||
|
||||
BedrockMovePlayerTranslator.translate(session, packet);
|
||||
|
||||
Set<PlayerAuthInputData> inputData = packet.getInputData();
|
||||
if (!inputData.isEmpty()) {
|
||||
for (PlayerAuthInputData input : inputData) {
|
||||
switch (input) {
|
||||
case PERFORM_ITEM_INTERACTION -> processItemUseTransaction(session, packet.getItemUseTransaction());
|
||||
case PERFORM_BLOCK_ACTIONS -> BedrockBlockActions.translate(session, packet.getPlayerActions());
|
||||
case START_SNEAKING -> {
|
||||
ServerboundPlayerCommandPacket startSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SNEAKING);
|
||||
session.sendDownstreamGamePacket(startSneakPacket);
|
||||
|
||||
session.startSneaking();
|
||||
}
|
||||
case STOP_SNEAKING -> {
|
||||
ServerboundPlayerCommandPacket stopSneakPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SNEAKING);
|
||||
session.sendDownstreamGamePacket(stopSneakPacket);
|
||||
|
||||
session.stopSneaking();
|
||||
}
|
||||
case START_SPRINTING -> {
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
ServerboundPlayerCommandPacket startSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_SPRINTING);
|
||||
session.sendDownstreamGamePacket(startSprintPacket);
|
||||
session.setSprinting(true);
|
||||
}
|
||||
}
|
||||
case STOP_SPRINTING -> {
|
||||
if (!entity.getFlag(EntityFlag.SWIMMING)) {
|
||||
ServerboundPlayerCommandPacket stopSprintPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.STOP_SPRINTING);
|
||||
session.sendDownstreamGamePacket(stopSprintPacket);
|
||||
}
|
||||
session.setSprinting(false);
|
||||
}
|
||||
case START_SWIMMING -> session.setSwimming(true);
|
||||
case STOP_SWIMMING -> session.setSwimming(false);
|
||||
case START_FLYING -> { // Since 1.20.30
|
||||
if (session.isCanFly()) {
|
||||
if (session.getGameMode() == GameMode.SPECTATOR) {
|
||||
// should already be flying
|
||||
session.sendAdventureSettings();
|
||||
break;
|
||||
}
|
||||
|
||||
if (session.getPlayerEntity().getFlag(EntityFlag.SWIMMING) && session.getCollisionManager().isPlayerInWater()) {
|
||||
// As of 1.18.1, Java Edition cannot fly while in water, but it can fly while crawling
|
||||
// If this isn't present, swimming on a 1.13.2 server and then attempting to fly will put you into a flying/swimming state that is invalid on JE
|
||||
session.sendAdventureSettings();
|
||||
break;
|
||||
}
|
||||
|
||||
session.setFlying(true);
|
||||
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(true));
|
||||
} else {
|
||||
// update whether we can fly
|
||||
session.sendAdventureSettings();
|
||||
// stop flying
|
||||
PlayerActionPacket stopFlyingPacket = new PlayerActionPacket();
|
||||
stopFlyingPacket.setRuntimeEntityId(session.getPlayerEntity().getGeyserId());
|
||||
stopFlyingPacket.setAction(PlayerActionType.STOP_FLYING);
|
||||
stopFlyingPacket.setBlockPosition(Vector3i.ZERO);
|
||||
stopFlyingPacket.setResultPosition(Vector3i.ZERO);
|
||||
stopFlyingPacket.setFace(0);
|
||||
session.sendUpstreamPacket(stopFlyingPacket);
|
||||
}
|
||||
}
|
||||
case STOP_FLYING -> {
|
||||
session.setFlying(false);
|
||||
session.sendDownstreamGamePacket(new ServerboundPlayerAbilitiesPacket(false));
|
||||
}
|
||||
case START_GLIDING -> {
|
||||
// Otherwise gliding will not work in creative
|
||||
ServerboundPlayerAbilitiesPacket playerAbilitiesPacket = new ServerboundPlayerAbilitiesPacket(false);
|
||||
session.sendDownstreamGamePacket(playerAbilitiesPacket);
|
||||
sendPlayerGlideToggle(session, entity);
|
||||
}
|
||||
case STOP_GLIDING -> sendPlayerGlideToggle(session, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendPlayerGlideToggle(GeyserSession session, Entity entity) {
|
||||
ServerboundPlayerCommandPacket glidePacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.START_ELYTRA_FLYING);
|
||||
session.sendDownstreamGamePacket(glidePacket);
|
||||
}
|
||||
|
||||
private static void processItemUseTransaction(GeyserSession session, ItemUseTransaction transaction) {
|
||||
if (transaction.getActionType() == 2) {
|
||||
int blockState = session.getGameMode() == GameMode.CREATIVE ?
|
||||
session.getGeyser().getWorldManager().getBlockAt(session, transaction.getBlockPosition()) : session.getBreakingBlock();
|
||||
|
||||
session.setLastBlockPlaced(null);
|
||||
session.setLastBlockPlacePosition(null);
|
||||
|
||||
// Same deal with vanilla block placing as above.
|
||||
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||
BedrockInventoryTransactionTranslator.restoreCorrectBlock(session, transaction.getBlockPosition());
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f playerPosition = session.getPlayerEntity().getPosition();
|
||||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
||||
|
||||
if (!BedrockInventoryTransactionTranslator.canInteractWithBlock(session, playerPosition, transaction.getBlockPosition())) {
|
||||
BedrockInventoryTransactionTranslator.restoreCorrectBlock(session, transaction.getBlockPosition());
|
||||
return;
|
||||
}
|
||||
|
||||
int sequence = session.getWorldCache().nextPredictionSequence();
|
||||
session.getWorldCache().markPositionInSequence(transaction.getBlockPosition());
|
||||
// -1 means we don't know what block they're breaking
|
||||
if (blockState == -1) {
|
||||
blockState = Block.JAVA_AIR_ID;
|
||||
}
|
||||
|
||||
LevelEventPacket blockBreakPacket = new LevelEventPacket();
|
||||
blockBreakPacket.setType(LevelEvent.PARTICLE_DESTROY_BLOCK);
|
||||
blockBreakPacket.setPosition(transaction.getBlockPosition().toFloat());
|
||||
blockBreakPacket.setData(session.getBlockMappings().getBedrockBlockId(blockState));
|
||||
session.sendUpstreamPacket(blockBreakPacket);
|
||||
session.setBreakingBlock(-1);
|
||||
|
||||
Entity itemFrameEntity = ItemFrameEntity.getItemFrameEntity(session, transaction.getBlockPosition());
|
||||
if (itemFrameEntity != null) {
|
||||
ServerboundInteractPacket attackPacket = new ServerboundInteractPacket(itemFrameEntity.getEntityId(),
|
||||
InteractAction.ATTACK, session.isSneaking());
|
||||
session.sendDownstreamGamePacket(attackPacket);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerAction action = session.getGameMode() == GameMode.CREATIVE ? PlayerAction.START_DIGGING : PlayerAction.FINISH_DIGGING;
|
||||
ServerboundPlayerActionPacket breakPacket = new ServerboundPlayerActionPacket(action, transaction.getBlockPosition(), Direction.VALUES[transaction.getBlockFace()], sequence);
|
||||
session.sendDownstreamGamePacket(breakPacket);
|
||||
} else {
|
||||
session.getGeyser().getLogger().error("Unhandled item use transaction type!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
package org.geysermc.geyser.translator.protocol.java;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import it.unimi.dsi.fastutil.Pair;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
|
@ -32,6 +33,7 @@ import net.kyori.adventure.key.Key;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.ShapedRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.recipe.SmithingTransformRecipeData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.DefaultDescriptor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
||||
|
@ -52,6 +54,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.RecipeDispla
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.RecipeDisplayEntry;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapedCraftingRecipeDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapelessCraftingRecipeDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.SmithingRecipeDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.CompositeSlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
|
||||
|
@ -61,11 +64,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.TagSlot
|
|||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundRecipeBookAddPacket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Translator(packet = ClientboundRecipeBookAddPacket.class)
|
||||
|
@ -104,17 +108,17 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
|
||||
boolean empty = true;
|
||||
boolean complexInputs = false;
|
||||
List<ItemDescriptorWithCount[]> inputs = new ArrayList<>(shapedRecipe.ingredients().size());
|
||||
List<List<ItemDescriptorWithCount>> inputs = new ArrayList<>(shapedRecipe.ingredients().size());
|
||||
for (SlotDisplay input : shapedRecipe.ingredients()) {
|
||||
ItemDescriptorWithCount[] translated = translateToInput(session, input);
|
||||
List<ItemDescriptorWithCount> translated = translateToInput(session, input);
|
||||
if (translated == null) {
|
||||
continue;
|
||||
}
|
||||
inputs.add(translated);
|
||||
if (translated.length != 1 || translated[0] != ItemDescriptorWithCount.EMPTY) {
|
||||
if (translated.size() != 1 || translated.get(0) != ItemDescriptorWithCount.EMPTY) {
|
||||
empty = false;
|
||||
}
|
||||
complexInputs |= translated.length > 1;
|
||||
complexInputs |= translated.size() > 1;
|
||||
}
|
||||
if (empty) {
|
||||
// Crashes Bedrock 1.19.70 otherwise
|
||||
|
@ -123,15 +127,31 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
}
|
||||
|
||||
if (complexInputs) {
|
||||
|
||||
} else {
|
||||
String recipeId = Integer.toString(contents.id());
|
||||
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
|
||||
shapedRecipe.width(), shapedRecipe.height(), inputs.stream().map(descriptors -> descriptors[0]).toList(),
|
||||
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
|
||||
recipesPacket.getUnlockedRecipes().add(recipeId);
|
||||
javaToBedrockRecipeIds.put(contents.id(), Collections.singletonList(recipeId));
|
||||
System.out.println(inputs);
|
||||
if (true) continue;
|
||||
List<List<ItemDescriptorWithCount>> processedInputs = Lists.cartesianProduct(inputs);
|
||||
System.out.println(processedInputs.size());
|
||||
if (processedInputs.size() <= 500) { // Do not let us process giant lists.
|
||||
List<String> bedrockRecipeIds = new ArrayList<>();
|
||||
for (int i = 0; i < processedInputs.size(); i++) {
|
||||
List<ItemDescriptorWithCount> possibleInput = processedInputs.get(i);
|
||||
String recipeId = contents.id() + "_" + i;
|
||||
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
|
||||
shapedRecipe.width(), shapedRecipe.height(), possibleInput,
|
||||
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
|
||||
recipesPacket.getUnlockedRecipes().add(recipeId);
|
||||
bedrockRecipeIds.add(recipeId);
|
||||
}
|
||||
javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
String recipeId = Integer.toString(contents.id());
|
||||
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
|
||||
shapedRecipe.width(), shapedRecipe.height(), inputs.stream().map(descriptors -> descriptors.get(0)).toList(),
|
||||
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
|
||||
recipesPacket.getUnlockedRecipes().add(recipeId);
|
||||
javaToBedrockRecipeIds.put(contents.id(), Collections.singletonList(recipeId));
|
||||
}
|
||||
case CRAFTING_SHAPELESS -> {
|
||||
ShapelessCraftingRecipeDisplay shapelessRecipe = (ShapelessCraftingRecipeDisplay) display;
|
||||
|
@ -147,6 +167,42 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
output = output.toBuilder().tag(null).build();
|
||||
}
|
||||
}
|
||||
case SMITHING -> {
|
||||
if (true) {
|
||||
System.out.println(display);
|
||||
continue;
|
||||
}
|
||||
SmithingRecipeDisplay smithingRecipe = (SmithingRecipeDisplay) display;
|
||||
Pair<Item, ItemData> output = translateToOutput(session, smithingRecipe.result());
|
||||
if (output == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ItemDescriptorWithCount> bases = translateToInput(session, smithingRecipe.base());
|
||||
List<ItemDescriptorWithCount> templates = translateToInput(session, smithingRecipe.template());
|
||||
List<ItemDescriptorWithCount> additions = translateToInput(session, smithingRecipe.addition());
|
||||
|
||||
if (bases == null || templates == null || additions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
List<String> bedrockRecipeIds = new ArrayList<>();
|
||||
for (ItemDescriptorWithCount template : templates) {
|
||||
for (ItemDescriptorWithCount base : bases) {
|
||||
for (ItemDescriptorWithCount addition : additions) {
|
||||
String id = contents.id() + "_" + i++;
|
||||
// Note: vanilla inputs use aux value of Short.MAX_VALUE
|
||||
craftingDataPacket.getCraftingData().add(SmithingTransformRecipeData.of(id,
|
||||
template, base, addition, output.right(), "smithing_table", netId++));
|
||||
|
||||
recipesPacket.getUnlockedRecipes().add(id);
|
||||
bedrockRecipeIds.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
javaToBedrockRecipeIds.put(contents.id(), bedrockRecipeIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,11 +215,11 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
TAG_TO_ITEM_DESCRIPTOR_CACHE.remove();
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Map<int[], ItemDescriptorWithCount[]>> TAG_TO_ITEM_DESCRIPTOR_CACHE = ThreadLocal.withInitial(Object2ObjectOpenHashMap::new);
|
||||
private static final ThreadLocal<Map<int[], List<ItemDescriptorWithCount>>> TAG_TO_ITEM_DESCRIPTOR_CACHE = ThreadLocal.withInitial(Object2ObjectOpenHashMap::new);
|
||||
|
||||
private ItemDescriptorWithCount[] translateToInput(GeyserSession session, SlotDisplay slotDisplay) {
|
||||
private List<ItemDescriptorWithCount> translateToInput(GeyserSession session, SlotDisplay slotDisplay) {
|
||||
if (slotDisplay instanceof EmptySlotDisplay) {
|
||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
|
||||
return Collections.singletonList(ItemDescriptorWithCount.EMPTY);
|
||||
}
|
||||
if (slotDisplay instanceof CompositeSlotDisplay composite) {
|
||||
if (composite.contents().size() == 1) {
|
||||
|
@ -172,23 +228,23 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
return composite.contents().stream()
|
||||
.map(subDisplay -> translateToInput(session, subDisplay))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Arrays::stream)
|
||||
.toArray(ItemDescriptorWithCount[]::new);
|
||||
.flatMap(List::stream)
|
||||
.toList();
|
||||
}
|
||||
if (slotDisplay instanceof ItemSlotDisplay itemSlot) {
|
||||
return new ItemDescriptorWithCount[] {fromItem(session, itemSlot.item())};
|
||||
return Collections.singletonList(fromItem(session, itemSlot.item()));
|
||||
}
|
||||
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlot) {
|
||||
ItemData item = ItemTranslator.translateToBedrock(session, itemStackSlot.itemStack());
|
||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.fromItem(item)};
|
||||
return Collections.singletonList(ItemDescriptorWithCount.fromItem(item));
|
||||
}
|
||||
if (slotDisplay instanceof TagSlotDisplay tagSlot) {
|
||||
Key tag = tagSlot.tag();
|
||||
int[] items = session.getTagCache().getRaw(new Tag<>(JavaRegistries.ITEM, tag)); // I don't like this...
|
||||
if (items == null || items.length == 0) {
|
||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
|
||||
return Collections.singletonList(ItemDescriptorWithCount.EMPTY);
|
||||
} else if (items.length == 1) {
|
||||
return new ItemDescriptorWithCount[] {fromItem(session, items[0])};
|
||||
return Collections.singletonList(fromItem(session, items[0]));
|
||||
} else {
|
||||
// Cache is implemented as, presumably, an item tag will be used multiple times in succession
|
||||
// (E.G. a chest with planks tags)
|
||||
|
@ -205,14 +261,14 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
// }).collect(Collectors.joining(" || "));
|
||||
// if ("minecraft:planks".equals(tag.toString())) {
|
||||
// String molang = "q.any_tag('minecraft:planks')";
|
||||
// return new ItemDescriptorWithCount[] {new ItemDescriptorWithCount(new MolangDescriptor(molang, 10), 1)};
|
||||
// return Collections.singletonList(new ItemDescriptorWithCount(new MolangDescriptor(molang, 10), 1));
|
||||
// }
|
||||
return null;
|
||||
// Set<ItemDescriptorWithCount> itemDescriptors = new HashSet<>();
|
||||
// for (int item : key) {
|
||||
// itemDescriptors.add(fromItem(session, item));
|
||||
// }
|
||||
// return itemDescriptors.toArray(ItemDescriptorWithCount[]::new);
|
||||
|
||||
Set<ItemDescriptorWithCount> itemDescriptors = new HashSet<>();
|
||||
for (int item : key) {
|
||||
itemDescriptors.add(fromItem(session, item));
|
||||
}
|
||||
return new ArrayList<>(itemDescriptors); // This, or a list from the start with contains -> add?
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -243,40 +299,4 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||
ItemMapping mapping = session.getItemMappings().getMapping(item);
|
||||
return new ItemDescriptorWithCount(new DefaultDescriptor(mapping.getBedrockDefinition(), mapping.getBedrockData()), 1); // Need to check count
|
||||
}
|
||||
|
||||
// private static ItemDescriptorWithCount[][] combinations(ItemDescriptorWithCount[] itemDescriptors) {
|
||||
// int totalCombinations = 1;
|
||||
// for (Set<ItemDescriptorWithCount> optionSet : squashedOptions.keySet()) {
|
||||
// totalCombinations *= optionSet.size();
|
||||
// }
|
||||
// if (totalCombinations > 500) {
|
||||
// ItemDescriptorWithCount[] translatedItems = new ItemDescriptorWithCount[ingredients.length];
|
||||
// for (int i = 0; i < ingredients.length; i++) {
|
||||
// if (ingredients[i].getOptions().length > 0) {
|
||||
// translatedItems[i] = ItemDescriptorWithCount.fromItem(ItemTranslator.translateToBedrock(session, ingredients[i].getOptions()[0]));
|
||||
// } else {
|
||||
// translatedItems[i] = ItemDescriptorWithCount.EMPTY;
|
||||
// }
|
||||
// }
|
||||
// return new ItemDescriptorWithCount[][]{translatedItems};
|
||||
// }
|
||||
// List<Set<ItemDescriptorWithCount>> sortedSets = new ArrayList<>(squashedOptions.keySet());
|
||||
// sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder()));
|
||||
// ItemDescriptorWithCount[][] combinations = new ItemDescriptorWithCount[totalCombinations][ingredients.length];
|
||||
// int x = 1;
|
||||
// for (Set<ItemDescriptorWithCount> set : sortedSets) {
|
||||
// IntSet slotSet = squashedOptions.get(set);
|
||||
// int i = 0;
|
||||
// for (ItemDescriptorWithCount item : set) {
|
||||
// for (int j = 0; j < totalCombinations / set.size(); j++) {
|
||||
// final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x);
|
||||
// for (IntIterator it = slotSet.iterator(); it.hasNext(); ) {
|
||||
// combinations[comboIndex][it.nextInt()] = item;
|
||||
// }
|
||||
// }
|
||||
// i++;
|
||||
// }
|
||||
// x *= set.size();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -154,10 +154,9 @@ public class JavaUpdateRecipesTranslator extends PacketTranslator<ClientboundUpd
|
|||
int recipeNetId = netId++;
|
||||
UUID uuid = UUID.randomUUID();
|
||||
// We need to register stonecutting recipes, so they show up on Bedrock
|
||||
// (Implementation note: recipe ID creates the order which stonecutting recipes are shown in stonecutter
|
||||
// (Implementation note: recipe ID creates the order which stonecutting recipes are shown in stonecutter)
|
||||
craftingDataPacket.getCraftingData().add(ShapelessRecipeData.shapeless("stonecutter_" + javaInput + "_" + buttonId,
|
||||
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, recipeNetId, RecipeUnlockingRequirement.INVALID));
|
||||
session.getGeyser().getLogger().info(mapping.getJavaItem().javaIdentifier() + " " + buttonId + " " + recipeNetId);
|
||||
|
||||
// Save the recipe list for reference when crafting
|
||||
// Add the net ID as the key and the button required + output for the value
|
||||
|
|
Loading…
Reference in a new issue