Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-16 13:00:10 +01:00
Migrate to SERVER-AUTHORITATIVE MOVEMENT dun dun dunnnn
Dieser Commit ist enthalten in:
Ursprung
52ce17dee6
Commit
e17ad64d8c
@ -27,6 +27,7 @@ package org.geysermc.geyser.level.physics;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import net.kyori.adventure.util.TriState;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.cloudburstmc.math.GenericMath;
|
import org.cloudburstmc.math.GenericMath;
|
||||||
import org.cloudburstmc.math.vector.Vector3d;
|
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.
|
* 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 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.
|
* @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
|
* @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();
|
PistonCache pistonCache = session.getPistonCache();
|
||||||
// Bedrock clients tend to fall off of honey blocks, so we need to teleport them to the new position
|
// Bedrock clients tend to fall off of honey blocks, so we need to teleport them to the new position
|
||||||
if (pistonCache.isPlayerAttachedToHoney()) {
|
if (pistonCache.isPlayerAttachedToHoney()) {
|
||||||
@ -176,7 +176,7 @@ public class CollisionManager {
|
|||||||
playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2);
|
playerBoundingBox.setMiddleY(position.getY() + playerBoundingBox.getSizeY() / 2);
|
||||||
playerBoundingBox.setMiddleZ(position.getZ());
|
playerBoundingBox.setMiddleZ(position.getZ());
|
||||||
|
|
||||||
return playerBoundingBox.getBottomCenter();
|
return new CollisionResult(playerBoundingBox.getBottomCenter(), TriState.NOT_SET);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
Vector3d startingPos = playerBoundingBox.getBottomCenter();
|
||||||
@ -198,9 +198,9 @@ public class CollisionManager {
|
|||||||
|
|
||||||
position = playerBoundingBox.getBottomCenter();
|
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
|
// 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();
|
PlayerEntity playerEntity = session.getPlayerEntity();
|
||||||
// Client will dismount if on a vehicle
|
// Client will dismount if on a vehicle
|
||||||
if (playerEntity.getVehicle() == null && pistonCache.getPlayerMotion().equals(Vector3f.ZERO) && !pistonCache.isPlayerSlimeCollision()) {
|
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
|
// 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());
|
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
|
// 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;
|
package org.geysermc.geyser.level.physics;
|
||||||
|
|
||||||
|
import net.kyori.adventure.util.TriState;
|
||||||
import org.cloudburstmc.math.vector.Vector3d;
|
import org.cloudburstmc.math.vector.Vector3d;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the result of a collision check.
|
* 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.NpcRequestPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PhotoInfoRequestPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PhotoInfoRequestPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PhotoTransferPacket;
|
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.PlayerHotbarPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PurchaseReceiptPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PurchaseReceiptPacket;
|
||||||
@ -318,7 +317,7 @@ class CodecProcessor {
|
|||||||
.updateSerializer(ClientCheatAbilityPacket.class, ILLEGAL_SERIALIZER)
|
.updateSerializer(ClientCheatAbilityPacket.class, ILLEGAL_SERIALIZER)
|
||||||
.updateSerializer(CraftingEventPacket.class, ILLEGAL_SERIALIZER)
|
.updateSerializer(CraftingEventPacket.class, ILLEGAL_SERIALIZER)
|
||||||
// Illegal unusued serverbound packets that relate to unused features
|
// 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(ClientCacheBlobStatusPacket.class, ILLEGAL_SERIALIZER)
|
||||||
.updateSerializer(SubClientLoginPacket.class, ILLEGAL_SERIALIZER)
|
.updateSerializer(SubClientLoginPacket.class, ILLEGAL_SERIALIZER)
|
||||||
.updateSerializer(SubChunkRequestPacket.class, ILLEGAL_SERIALIZER)
|
.updateSerializer(SubChunkRequestPacket.class, ILLEGAL_SERIALIZER)
|
||||||
|
@ -290,7 +290,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PacketSignal handle(MovePlayerPacket packet) {
|
public PacketSignal handle(MovePlayerPacket packet) { // TODO
|
||||||
if (session.isLoggingIn()) {
|
if (session.isLoggingIn()) {
|
||||||
SetTitlePacket titlePacket = new SetTitlePacket();
|
SetTitlePacket titlePacket = new SetTitlePacket();
|
||||||
titlePacket.setType(SetTitlePacket.Type.ACTIONBAR);
|
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.EntityCache;
|
||||||
import org.geysermc.geyser.session.cache.EntityEffectCache;
|
import org.geysermc.geyser.session.cache.EntityEffectCache;
|
||||||
import org.geysermc.geyser.session.cache.FormCache;
|
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.LodestoneCache;
|
||||||
import org.geysermc.geyser.session.cache.PistonCache;
|
import org.geysermc.geyser.session.cache.PistonCache;
|
||||||
import org.geysermc.geyser.session.cache.PreferencesCache;
|
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.handshake.serverbound.ClientIntentionPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatCommandSignedPacket;
|
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.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.ServerboundPlayerAbilitiesPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundPlayerActionPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemPacket;
|
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 EntityCache entityCache;
|
||||||
private final EntityEffectCache effectCache;
|
private final EntityEffectCache effectCache;
|
||||||
private final FormCache formCache;
|
private final FormCache formCache;
|
||||||
|
private final InputCache inputCache;
|
||||||
private final LodestoneCache lodestoneCache;
|
private final LodestoneCache lodestoneCache;
|
||||||
private final PistonCache pistonCache;
|
private final PistonCache pistonCache;
|
||||||
private final PreferencesCache preferencesCache;
|
private final PreferencesCache preferencesCache;
|
||||||
@ -523,12 +524,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
@Setter
|
@Setter
|
||||||
private boolean placedBucket;
|
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
|
* 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.entityCache = new EntityCache(this);
|
||||||
this.effectCache = new EntityEffectCache();
|
this.effectCache = new EntityEffectCache();
|
||||||
this.formCache = new FormCache(this);
|
this.formCache = new FormCache(this);
|
||||||
|
this.inputCache = new InputCache(this);
|
||||||
this.lodestoneCache = new LodestoneCache();
|
this.lodestoneCache = new LodestoneCache();
|
||||||
this.pistonCache = new PistonCache(this);
|
this.pistonCache = new PistonCache(this);
|
||||||
this.preferencesCache = new PreferencesCache(this);
|
this.preferencesCache = new PreferencesCache(this);
|
||||||
@ -1266,18 +1262,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
protected void tick() {
|
protected void tick() {
|
||||||
try {
|
try {
|
||||||
pistonCache.tick();
|
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()) {
|
if (worldBorder.isResizing()) {
|
||||||
worldBorder.resize();
|
worldBorder.resize();
|
||||||
@ -1668,7 +1652,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||||||
|
|
||||||
startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE);
|
startGamePacket.setChatRestrictionLevel(ChatRestrictionLevel.NONE);
|
||||||
|
|
||||||
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT);
|
startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.SERVER);
|
||||||
startGamePacket.setRewindHistorySize(0);
|
startGamePacket.setRewindHistorySize(0);
|
||||||
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
startGamePacket.setServerAuthoritativeBlockBreaking(false);
|
||||||
|
|
||||||
|
80
core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java
vendored
Normale Datei
80
core/src/main/java/org/geysermc/geyser/session/cache/InputCache.java
vendored
Normale Datei
@ -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.Vector3d;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
import org.cloudburstmc.math.vector.Vector3f;
|
||||||
import org.cloudburstmc.math.vector.Vector3i;
|
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.SoundEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
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.data.inventory.transaction.LegacySetItemSlotData;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket;
|
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.LevelSoundEventPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||||
@ -187,7 +185,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
default -> false;
|
default -> false;
|
||||||
};
|
};
|
||||||
if (isGodBridging) {
|
if (isGodBridging) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +205,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, belowBlockPos);
|
int belowBlock = session.getGeyser().getWorldManager().getBlockAt(session, belowBlockPos);
|
||||||
BlockDefinition extendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
|
BlockDefinition extendedCollisionDefinition = session.getBlockMappings().getExtendedCollisionBoxes().get(belowBlock);
|
||||||
if (extendedCollisionDefinition != null && (System.currentTimeMillis() - session.getLastInteractionTime()) < 200) {
|
if (extendedCollisionDefinition != null && (System.currentTimeMillis() - session.getLastInteractionTime()) < 200) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,7 +225,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isIncorrectHeldItem(session, packet)) {
|
if (isIncorrectHeldItem(session, packet)) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +245,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
*/
|
*/
|
||||||
// Blocks cannot be placed or destroyed outside of the world border
|
// Blocks cannot be placed or destroyed outside of the world border
|
||||||
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
if (!session.getWorldBorder().isInsideBorderBoundaries()) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +254,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
playerPosition = playerPosition.down(EntityDefinitions.PLAYER.offset() - session.getEyeHeight());
|
||||||
|
|
||||||
if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
|
if (!canInteractWithBlock(session, playerPosition, packetBlockPosition)) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +268,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
double clickDistanceY = clickPositionFullY - blockCenter.getY();
|
double clickDistanceY = clickPositionFullY - blockCenter.getY();
|
||||||
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
|
double clickDistanceZ = clickPositionFullZ - blockCenter.getZ();
|
||||||
if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) {
|
if (!(Math.abs(clickDistanceX) < 1.0000001D && Math.abs(clickDistanceY) < 1.0000001D && Math.abs(clickDistanceZ) < 1.0000001D)) {
|
||||||
restoreCorrectBlock(session, blockPos, packet);
|
restoreCorrectBlock(session, blockPos);
|
||||||
return;
|
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;
|
break;
|
||||||
case ITEM_RELEASE:
|
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.
|
// ViaVersion sends this 1.20.5+ attribute also, so older servers will have correct range checks.
|
||||||
double blockInteractionRange = session.getPlayerEntity().getBlockInteractionRange();
|
double blockInteractionRange = session.getPlayerEntity().getBlockInteractionRange();
|
||||||
|
|
||||||
@ -578,7 +529,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
* @param session the session of the Bedrock client
|
* @param session the session of the Bedrock client
|
||||||
* @param blockPos the block position to restore
|
* @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);
|
BlockState javaBlockState = session.getGeyser().getWorldManager().blockAt(session, blockPos);
|
||||||
BlockDefinition bedrockBlock = session.getBlockMappings().getBedrockBlock(javaBlockState);
|
BlockDefinition bedrockBlock = session.getBlockMappings().getBedrockBlock(javaBlockState);
|
||||||
|
|
||||||
@ -605,7 +556,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
|
|||||||
session.sendUpstreamPacket(updateWaterPacket);
|
session.sendUpstreamPacket(updateWaterPacket);
|
||||||
|
|
||||||
// Reset the item in hand to prevent "missing" blocks
|
// 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) {
|
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));
|
float pitch = (float) -Math.toDegrees(Math.atan2(yDiff, xzHypot));
|
||||||
|
|
||||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
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
|
// 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);
|
session.sendDownstreamGamePacket(movementPacket);
|
||||||
|
|
||||||
if (session.getLookBackScheduledFuture() != null) {
|
if (session.getLookBackScheduledFuture() != null) {
|
||||||
|
@ -29,14 +29,11 @@ import org.cloudburstmc.math.vector.Vector3f;
|
|||||||
import org.cloudburstmc.math.vector.Vector3i;
|
import org.cloudburstmc.math.vector.Vector3i;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.PlayerActionType;
|
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.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.AnimatePacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.PlayerActionPacket;
|
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.UpdateAttributesPacket;
|
||||||
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
import org.geysermc.geyser.api.block.custom.CustomBlockState;
|
||||||
import org.geysermc.geyser.entity.type.Entity;
|
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.entity.type.player.SessionPlayerEntity;
|
||||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||||
import org.geysermc.geyser.level.block.Blocks;
|
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.Block;
|
||||||
import org.geysermc.geyser.level.block.type.BlockState;
|
import org.geysermc.geyser.level.block.type.BlockState;
|
||||||
import org.geysermc.geyser.registry.BlockRegistries;
|
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.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.SkullCache;
|
import org.geysermc.geyser.session.cache.SkullCache;
|
||||||
import org.geysermc.geyser.translator.item.CustomItemTranslator;
|
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.BlockUtils;
|
||||||
import org.geysermc.geyser.util.CooldownUtils;
|
import org.geysermc.geyser.util.CooldownUtils;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction;
|
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.Hand;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.InteractAction;
|
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.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.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.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.ServerboundSwingPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundUseItemOnPacket;
|
|
||||||
|
|
||||||
@Translator(packet = PlayerActionPacket.class)
|
import java.util.List;
|
||||||
public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket> {
|
|
||||||
|
|
||||||
@Override
|
final class BedrockBlockActions {
|
||||||
public void translate(GeyserSession session, PlayerActionPacket packet) {
|
|
||||||
|
static void translate(GeyserSession session, List<PlayerBlockActionData> playerActions) {
|
||||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||||
|
|
||||||
// Send book update before any player action
|
// 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()) {
|
switch (action) {
|
||||||
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);
|
|
||||||
}
|
|
||||||
case DROP_ITEM -> {
|
case DROP_ITEM -> {
|
||||||
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
|
ServerboundPlayerActionPacket dropItemPacket = new ServerboundPlayerActionPacket(PlayerAction.DROP_ITEM,
|
||||||
vector, Direction.VALUES[packet.getFace()], 0);
|
vector, Direction.VALUES[blockFace], 0);
|
||||||
session.sendDownstreamGamePacket(dropItemPacket);
|
session.sendDownstreamGamePacket(dropItemPacket);
|
||||||
}
|
}
|
||||||
case STOP_SLEEP -> {
|
|
||||||
ServerboundPlayerCommandPacket stopSleepingPacket = new ServerboundPlayerCommandPacket(entity.getEntityId(), PlayerState.LEAVE_BED);
|
|
||||||
session.sendDownstreamGamePacket(stopSleepingPacket);
|
|
||||||
}
|
|
||||||
case START_BREAK -> {
|
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
|
// 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) {
|
if (session.getGameMode() == GameMode.CREATIVE) {
|
||||||
@ -191,9 +114,9 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
session.sendUpstreamPacket(startBreak);
|
session.sendUpstreamPacket(startBreak);
|
||||||
|
|
||||||
// Account for fire - the client likes to hit the block behind.
|
// 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();
|
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) {
|
if (block == Blocks.FIRE || block == Blocks.SOUL_FIRE) {
|
||||||
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
|
ServerboundPlayerActionPacket startBreakingPacket = new ServerboundPlayerActionPacket(PlayerAction.START_DIGGING, fireBlockPos,
|
||||||
direction, session.getWorldCache().nextPredictionSequence());
|
direction, session.getWorldCache().nextPredictionSequence());
|
||||||
@ -218,7 +141,7 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
Vector3f vectorFloat = vector.toFloat();
|
Vector3f vectorFloat = vector.toFloat();
|
||||||
|
|
||||||
BlockState breakingBlockState = BlockState.of(breakingBlock);
|
BlockState breakingBlockState = BlockState.of(breakingBlock);
|
||||||
Direction direction = Direction.VALUES[packet.getFace()];
|
Direction direction = Direction.VALUES[blockFace];
|
||||||
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
|
spawnBlockBreakParticles(session, direction, vector, breakingBlockState);
|
||||||
|
|
||||||
double breakTime = BlockUtils.getSessionBreakTime(session, breakingBlockState.block()) * 20;
|
double breakTime = BlockUtils.getSessionBreakTime(session, breakingBlockState.block()) * 20;
|
||||||
@ -304,69 +227,10 @@ public class BedrockActionTranslator extends PacketTranslator<PlayerActionPacket
|
|||||||
session.sendUpstreamPacket(animatePacket);
|
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();
|
LevelEventPacket levelEventPacket = new LevelEventPacket();
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case UP -> levelEventPacket.setType(LevelEvent.PARTICLE_BREAK_BLOCK_UP);
|
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());
|
levelEventPacket.setData(session.getBlockMappings().getBedrockBlock(blockState).getRuntimeId());
|
||||||
session.sendUpstreamPacket(levelEventPacket);
|
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.Vector3d;
|
||||||
import org.cloudburstmc.math.vector.Vector3f;
|
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.EntityDefinitions;
|
||||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||||
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
import org.geysermc.geyser.entity.vehicle.ClientVehicle;
|
||||||
|
import org.geysermc.geyser.level.physics.CollisionResult;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.text.ChatColor;
|
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.network.packet.Packet;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerPosPacket;
|
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.ServerboundMovePlayerPosRotPacket;
|
||||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.player.ServerboundMovePlayerRotPacket;
|
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 final class BedrockMovePlayerTranslator {
|
||||||
public void translate(GeyserSession session, MovePlayerPacket packet) {
|
|
||||||
|
static void translate(GeyserSession session, PlayerAuthInputPacket packet) {
|
||||||
SessionPlayerEntity entity = session.getPlayerEntity();
|
SessionPlayerEntity entity = session.getPlayerEntity();
|
||||||
if (!session.isSpawned()) return;
|
if (!session.isSpawned()) return;
|
||||||
|
|
||||||
session.setLastMovementTimestamp(System.currentTimeMillis());
|
|
||||||
|
|
||||||
// Send book update before the player moves
|
// Send book update before the player moves
|
||||||
session.getBookEditCache().checkForSend();
|
session.getBookEditCache().checkForSend();
|
||||||
|
|
||||||
|
boolean actualPositionChanged = !entity.getPosition().equals(packet.getPosition());
|
||||||
// Ignore movement packets until Bedrock's position matches the teleported position
|
// 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));
|
session.confirmTeleport(packet.getPosition().toDouble().sub(0, EntityDefinitions.PLAYER.offset(), 0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -70,7 +69,8 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
float pitch = packet.getRotation().getX();
|
float pitch = packet.getRotation().getX();
|
||||||
float headYaw = packet.getRotation().getY();
|
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;
|
boolean rotationChanged = entity.getYaw() != yaw || entity.getPitch() != pitch || entity.getHeadYaw() != headYaw;
|
||||||
|
|
||||||
if (session.getLookBackScheduledFuture() != null) {
|
if (session.getLookBackScheduledFuture() != null) {
|
||||||
@ -80,19 +80,22 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
session.setLookBackScheduledFuture(null);
|
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
|
// If only the pitch and yaw changed
|
||||||
// This isn't needed, but it makes the packets closer to vanilla
|
// 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
|
// It also means you can't "lag back" while only looking, in theory
|
||||||
if (!positionChanged && rotationChanged) {
|
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.setYaw(yaw);
|
||||||
entity.setPitch(pitch);
|
entity.setPitch(pitch);
|
||||||
entity.setHeadYaw(headYaw);
|
entity.setHeadYaw(headYaw);
|
||||||
entity.setOnGround(packet.isOnGround());
|
|
||||||
|
|
||||||
session.sendDownstreamGamePacket(playerRotationPacket);
|
session.sendDownstreamGamePacket(playerRotationPacket);
|
||||||
} else {
|
} else if (positionChanged) {
|
||||||
// World border collision will be handled by client vehicle
|
// World border collision will be handled by client vehicle
|
||||||
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
|
if (!(entity.getVehicle() instanceof ClientVehicle clientVehicle && clientVehicle.isClientControlled())
|
||||||
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
&& session.getWorldBorder().isPassingIntoBorderBoundaries(packet.getPosition(), true)) {
|
||||||
@ -100,9 +103,10 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
|
if (isValidMove(session, entity.getPosition(), packet.getPosition())) {
|
||||||
Vector3d position = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.isOnGround(), packet.getMode() == MovePlayerPacket.Mode.TELEPORT);
|
CollisionResult result = session.getCollisionManager().adjustBedrockPosition(packet.getPosition(), packet.getInputData().contains(PlayerAuthInputData.HANDLE_TELEPORT));
|
||||||
if (position != null) { // A null return value cancels the packet
|
if (result != null) { // A null return value cancels the packet
|
||||||
boolean onGround = packet.isOnGround();
|
Vector3d position = result.correctedMovement();
|
||||||
|
boolean onGround = result.onGround().toBooleanOrElse(entity.isOnGround());
|
||||||
boolean isBelowVoid = entity.isVoidPositionDesynched();
|
boolean isBelowVoid = entity.isVoidPositionDesynched();
|
||||||
|
|
||||||
boolean teleportThroughVoidFloor, mustResyncPosition;
|
boolean teleportThroughVoidFloor, mustResyncPosition;
|
||||||
@ -138,7 +142,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
// Send rotation updates as well
|
// Send rotation updates as well
|
||||||
movePacket = new ServerboundMovePlayerPosRotPacket(
|
movePacket = new ServerboundMovePlayerPosRotPacket(
|
||||||
onGround,
|
onGround,
|
||||||
false,
|
horizontalCollision,
|
||||||
position.getX(), yPosition, position.getZ(),
|
position.getX(), yPosition, position.getZ(),
|
||||||
yaw, pitch
|
yaw, pitch
|
||||||
);
|
);
|
||||||
@ -147,7 +151,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
entity.setHeadYaw(headYaw);
|
entity.setHeadYaw(headYaw);
|
||||||
} else {
|
} else {
|
||||||
// Rotation did not change; don't send an update with rotation
|
// 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());
|
entity.setPositionManual(packet.getPosition());
|
||||||
@ -162,6 +166,7 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
entity.teleportVoidFloorFix(true);
|
entity.teleportVoidFloorFix(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session.getInputCache().markPositionPacketSent();
|
||||||
session.getSkullCache().updateVisibleSkulls();
|
session.getSkullCache().updateVisibleSkulls();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -169,8 +174,12 @@ public class BedrockMovePlayerTranslator extends PacketTranslator<MovePlayerPack
|
|||||||
session.getGeyser().getLogger().debug("Recalculating position...");
|
session.getGeyser().getLogger().debug("Recalculating position...");
|
||||||
session.getCollisionManager().recalculatePosition();
|
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
|
// Move parrots to match if applicable
|
||||||
if (entity.getLeftParrot() != null) {
|
if (entity.getLeftParrot() != null) {
|
||||||
entity.getLeftParrot().moveAbsolute(entity.getPosition(), entity.getYaw(), entity.getPitch(), entity.getHeadYaw(), true, false);
|
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);
|
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())) {
|
if (isInvalidNumber(newPosition.getX()) || isInvalidNumber(newPosition.getY()) || isInvalidNumber(newPosition.getZ())) {
|
||||||
return false;
|
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;
|
package org.geysermc.geyser.translator.protocol.java;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import it.unimi.dsi.fastutil.Pair;
|
import it.unimi.dsi.fastutil.Pair;
|
||||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
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.ItemData;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.crafting.RecipeUnlockingRequirement;
|
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.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.DefaultDescriptor;
|
||||||
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
|
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.RecipeDisplayEntry;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.ShapedCraftingRecipeDisplay;
|
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.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.CompositeSlotDisplay;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
|
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSlotDisplay;
|
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 org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundRecipeBookAddPacket;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Translator(packet = ClientboundRecipeBookAddPacket.class)
|
@Translator(packet = ClientboundRecipeBookAddPacket.class)
|
||||||
@ -104,17 +108,17 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||||||
|
|
||||||
boolean empty = true;
|
boolean empty = true;
|
||||||
boolean complexInputs = false;
|
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()) {
|
for (SlotDisplay input : shapedRecipe.ingredients()) {
|
||||||
ItemDescriptorWithCount[] translated = translateToInput(session, input);
|
List<ItemDescriptorWithCount> translated = translateToInput(session, input);
|
||||||
if (translated == null) {
|
if (translated == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
inputs.add(translated);
|
inputs.add(translated);
|
||||||
if (translated.length != 1 || translated[0] != ItemDescriptorWithCount.EMPTY) {
|
if (translated.size() != 1 || translated.get(0) != ItemDescriptorWithCount.EMPTY) {
|
||||||
empty = false;
|
empty = false;
|
||||||
}
|
}
|
||||||
complexInputs |= translated.length > 1;
|
complexInputs |= translated.size() > 1;
|
||||||
}
|
}
|
||||||
if (empty) {
|
if (empty) {
|
||||||
// Crashes Bedrock 1.19.70 otherwise
|
// Crashes Bedrock 1.19.70 otherwise
|
||||||
@ -123,15 +127,31 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (complexInputs) {
|
if (complexInputs) {
|
||||||
|
System.out.println(inputs);
|
||||||
} else {
|
if (true) continue;
|
||||||
String recipeId = Integer.toString(contents.id());
|
List<List<ItemDescriptorWithCount>> processedInputs = Lists.cartesianProduct(inputs);
|
||||||
craftingDataPacket.getCraftingData().add(ShapedRecipeData.shaped(recipeId,
|
System.out.println(processedInputs.size());
|
||||||
shapedRecipe.width(), shapedRecipe.height(), inputs.stream().map(descriptors -> descriptors[0]).toList(),
|
if (processedInputs.size() <= 500) { // Do not let us process giant lists.
|
||||||
Collections.singletonList(output), UUID.randomUUID(), "crafting_table", 0, netId++, false, RecipeUnlockingRequirement.INVALID));
|
List<String> bedrockRecipeIds = new ArrayList<>();
|
||||||
recipesPacket.getUnlockedRecipes().add(recipeId);
|
for (int i = 0; i < processedInputs.size(); i++) {
|
||||||
javaToBedrockRecipeIds.put(contents.id(), Collections.singletonList(recipeId));
|
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 -> {
|
case CRAFTING_SHAPELESS -> {
|
||||||
ShapelessCraftingRecipeDisplay shapelessRecipe = (ShapelessCraftingRecipeDisplay) display;
|
ShapelessCraftingRecipeDisplay shapelessRecipe = (ShapelessCraftingRecipeDisplay) display;
|
||||||
@ -147,6 +167,42 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||||||
output = output.toBuilder().tag(null).build();
|
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();
|
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) {
|
if (slotDisplay instanceof EmptySlotDisplay) {
|
||||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
|
return Collections.singletonList(ItemDescriptorWithCount.EMPTY);
|
||||||
}
|
}
|
||||||
if (slotDisplay instanceof CompositeSlotDisplay composite) {
|
if (slotDisplay instanceof CompositeSlotDisplay composite) {
|
||||||
if (composite.contents().size() == 1) {
|
if (composite.contents().size() == 1) {
|
||||||
@ -172,23 +228,23 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||||||
return composite.contents().stream()
|
return composite.contents().stream()
|
||||||
.map(subDisplay -> translateToInput(session, subDisplay))
|
.map(subDisplay -> translateToInput(session, subDisplay))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.flatMap(Arrays::stream)
|
.flatMap(List::stream)
|
||||||
.toArray(ItemDescriptorWithCount[]::new);
|
.toList();
|
||||||
}
|
}
|
||||||
if (slotDisplay instanceof ItemSlotDisplay itemSlot) {
|
if (slotDisplay instanceof ItemSlotDisplay itemSlot) {
|
||||||
return new ItemDescriptorWithCount[] {fromItem(session, itemSlot.item())};
|
return Collections.singletonList(fromItem(session, itemSlot.item()));
|
||||||
}
|
}
|
||||||
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlot) {
|
if (slotDisplay instanceof ItemStackSlotDisplay itemStackSlot) {
|
||||||
ItemData item = ItemTranslator.translateToBedrock(session, itemStackSlot.itemStack());
|
ItemData item = ItemTranslator.translateToBedrock(session, itemStackSlot.itemStack());
|
||||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.fromItem(item)};
|
return Collections.singletonList(ItemDescriptorWithCount.fromItem(item));
|
||||||
}
|
}
|
||||||
if (slotDisplay instanceof TagSlotDisplay tagSlot) {
|
if (slotDisplay instanceof TagSlotDisplay tagSlot) {
|
||||||
Key tag = tagSlot.tag();
|
Key tag = tagSlot.tag();
|
||||||
int[] items = session.getTagCache().getRaw(new Tag<>(JavaRegistries.ITEM, tag)); // I don't like this...
|
int[] items = session.getTagCache().getRaw(new Tag<>(JavaRegistries.ITEM, tag)); // I don't like this...
|
||||||
if (items == null || items.length == 0) {
|
if (items == null || items.length == 0) {
|
||||||
return new ItemDescriptorWithCount[] {ItemDescriptorWithCount.EMPTY};
|
return Collections.singletonList(ItemDescriptorWithCount.EMPTY);
|
||||||
} else if (items.length == 1) {
|
} else if (items.length == 1) {
|
||||||
return new ItemDescriptorWithCount[] {fromItem(session, items[0])};
|
return Collections.singletonList(fromItem(session, items[0]));
|
||||||
} else {
|
} else {
|
||||||
// Cache is implemented as, presumably, an item tag will be used multiple times in succession
|
// Cache is implemented as, presumably, an item tag will be used multiple times in succession
|
||||||
// (E.G. a chest with planks tags)
|
// (E.G. a chest with planks tags)
|
||||||
@ -205,14 +261,14 @@ public class JavaRecipeBookAddTranslator extends PacketTranslator<ClientboundRec
|
|||||||
// }).collect(Collectors.joining(" || "));
|
// }).collect(Collectors.joining(" || "));
|
||||||
// if ("minecraft:planks".equals(tag.toString())) {
|
// if ("minecraft:planks".equals(tag.toString())) {
|
||||||
// String molang = "q.any_tag('minecraft:planks')";
|
// 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<>();
|
Set<ItemDescriptorWithCount> itemDescriptors = new HashSet<>();
|
||||||
// for (int item : key) {
|
for (int item : key) {
|
||||||
// itemDescriptors.add(fromItem(session, item));
|
itemDescriptors.add(fromItem(session, item));
|
||||||
// }
|
}
|
||||||
// return itemDescriptors.toArray(ItemDescriptorWithCount[]::new);
|
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);
|
ItemMapping mapping = session.getItemMappings().getMapping(item);
|
||||||
return new ItemDescriptorWithCount(new DefaultDescriptor(mapping.getBedrockDefinition(), mapping.getBedrockData()), 1); // Need to check count
|
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++;
|
int recipeNetId = netId++;
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
// We need to register stonecutting recipes, so they show up on Bedrock
|
// 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,
|
craftingDataPacket.getCraftingData().add(ShapelessRecipeData.shapeless("stonecutter_" + javaInput + "_" + buttonId,
|
||||||
Collections.singletonList(descriptor), Collections.singletonList(output), uuid, "stonecutter", 0, recipeNetId, RecipeUnlockingRequirement.INVALID));
|
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
|
// Save the recipe list for reference when crafting
|
||||||
// Add the net ID as the key and the button required + output for the value
|
// Add the net ID as the key and the button required + output for the value
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren