diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java index 47ae6777a..f53525503 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java @@ -187,7 +187,7 @@ public class BoatEntity extends Entity implements Leashable, Tickable { @Override public void tick() { // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing - doTick = !doTick; // Run every 100 ms + doTick = !doTick; // Run every other tick if (!doTick || passengers.isEmpty()) { return; } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index 499084555..6ecb1ce5a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -422,3 +422,4 @@ public class LivingEntity extends Entity { return type.getAttribute((float) AttributeUtils.calculateValue(javaAttribute)); } } + diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java index 55334010f..4724c84dc 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java @@ -25,12 +25,12 @@ package org.geysermc.geyser.entity.type; -import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; -import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import org.cloudburstmc.math.vector.Vector3f; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; +import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack; import java.util.UUID; @@ -48,6 +48,10 @@ public class ThrowableItemEntity extends ThrowableEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); setFlag(EntityFlag.INVISIBLE, true); invisible = false; + if (session.isFrozen()) { + age = 4; + checkVisibility(); + } } private void checkVisibility() { diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java index 25dd21662..9818b5211 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -550,7 +550,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { /** * Stores cookies sent by the Java server. */ - @Setter @Getter + @Setter + @Getter private Map cookies = new Object2ObjectOpenHashMap<>(); private final GeyserCameraData cameraData; @@ -559,6 +560,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private MinecraftProtocol protocol; + private float tickRate = 20.0f; + + private boolean frozen = false; + public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop eventLoop) { this.geyser = geyser; this.upstream = new UpstreamSession(bedrockServerSession); @@ -656,7 +661,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Default move speed // Bedrock clients move very fast by default until they get an attribute packet correcting the speed attributesPacket.setAttributes(Collections.singletonList( - GeyserAttributeType.MOVEMENT_SPEED.getAttribute())); + GeyserAttributeType.MOVEMENT_SPEED.getAttribute())); upstream.sendPacket(attributesPacket); GameRulesChangedPacket gamerulePacket = new GameRulesChangedPacket(); @@ -759,7 +764,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { sendUpstreamPacket(packet); final PendingMicrosoftAuthentication.AuthenticationTask task = geyser.getPendingMicrosoftAuthentication().getOrCreateTask( - getAuthData().xuid() + getAuthData().xuid() ); task.setOnline(true); task.resetTimer(); @@ -800,13 +805,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { GameProfile selectedProfile = service.getSelectedProfile(); if (selectedProfile == null) { disconnect(GeyserLocale.getPlayerLocaleString( - "geyser.network.remote.invalid_account", - clientData.getLanguageCode() + "geyser.network.remote.invalid_account", + clientData.getLanguageCode() )); } else { this.protocol = new MinecraftProtocol( - selectedProfile, - service.getAccessToken() + selectedProfile, + service.getAccessToken() ); try { connectDownstream(); @@ -831,7 +836,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { GeyserImpl.getInstance().eventBus().fire(loginEvent); if (loginEvent.isCancelled()) { String disconnectReason = loginEvent.disconnectReason() == null ? - BedrockDisconnectReasons.DISCONNECTED : loginEvent.disconnectReason(); + BedrockDisconnectReasons.DISCONNECTED : loginEvent.disconnectReason(); disconnect(disconnectReason); return; } @@ -841,7 +846,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE; // Start ticking - tickThread = eventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); + tickThread = eventLoop.scheduleAtFixedRate(this::tick, Math.round(1000 / tickRate), Math.round(1000 / tickRate), TimeUnit.MILLISECONDS); this.protocol.setUseDefaultListeners(false); @@ -849,8 +854,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (geyser.getBootstrap().getSocketAddress() != null) { // We're going to connect through the JVM and not through TCP downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(), - geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), - this.protocol, this.protocol.createHelper()); + geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), + this.protocol, this.protocol.createHelper()); this.downstream = new DownstreamSession(downstream); } else { downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), this.protocol); @@ -917,16 +922,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } encryptedData = cipher.encryptFromString(BedrockData.of( - clientData.getGameVersion(), - authData.name(), - authData.xuid(), - clientData.getDeviceOs().ordinal(), - clientData.getLanguageCode(), - clientData.getUiProfile().ordinal(), - clientData.getCurrentInputMode().ordinal(), - bedrockAddress, - skinUploader.getId(), - skinUploader.getVerifyCode() + clientData.getGameVersion(), + authData.name(), + authData.xuid(), + clientData.getDeviceOs().ordinal(), + clientData.getLanguageCode(), + clientData.getUiProfile().ordinal(), + clientData.getCurrentInputMode().ordinal(), + bedrockAddress, + skinUploader.getId(), + skinUploader.getVerifyCode() ).toString()); } catch (Exception e) { geyser.getLogger().error(GeyserLocale.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -960,11 +965,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { if (downstream instanceof LocalSession) { // Connected directly to the server geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect_internal", - authData.name(), protocol.getProfile().getName())); + authData.name(), protocol.getProfile().getName())); } else { // Connected to an IP address geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.remote.connect", - authData.name(), protocol.getProfile().getName(), remoteServer.address())); + authData.name(), protocol.getProfile().getName(), remoteServer.address())); } UUID uuid = protocol.getProfile().getId(); @@ -1004,10 +1009,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { disconnectMessage = GeyserLocale.getPlayerLocaleString("geyser.network.remote.authentication_type_mismatch", locale()); // Explain that they may be looking for Floodgate. geyser.getLogger().warning(GeyserLocale.getLocaleStringLog( - geyser.getPlatformType() == PlatformType.STANDALONE ? - "geyser.network.remote.floodgate_explanation_standalone" - : "geyser.network.remote.floodgate_explanation_plugin", - Constants.FLOODGATE_DOWNLOAD_LOCATION + geyser.getPlatformType() == PlatformType.STANDALONE ? + "geyser.network.remote.floodgate_explanation_standalone" + : "geyser.network.remote.floodgate_explanation_plugin", + Constants.FLOODGATE_DOWNLOAD_LOCATION )); } else { // Likely that Floodgate is not configured correctly. @@ -1070,6 +1075,13 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { downstream.connect(false, loginEvent.transferring()); } + public void updateTickData(float tickRate, boolean frozen) { + tickThread.cancel(true); + this.tickRate = tickRate; + this.frozen = frozen; + tickThread = eventLoop.scheduleAtFixedRate(this::tick, Math.round(1000 / getTickRate()), Math.round(1000 / getTickRate()), TimeUnit.MILLISECONDS); + } + public void disconnect(String reason) { if (!closed) { loggedIn = false; @@ -1159,7 +1171,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } /** - * Called every 50 milliseconds - one Minecraft tick. + * Called every Minecraft tick - 1000/tickRate milliseconds. */ protected void tick() { try { @@ -1171,7 +1183,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // A null return value cancels the packet if (position != null) { ServerboundMovePlayerPosPacket packet = new ServerboundMovePlayerPosPacket(playerEntity.isOnGround(), - position.getX(), position.getY(), position.getZ()); + position.getX(), position.getY(), position.getZ()); sendDownstreamGamePacket(packet); } lastMovementTimestamp = System.currentTimeMillis(); @@ -1197,11 +1209,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { isInWorldBorderWarningArea = false; } - - for (Tickable entity : entityCache.getTickableEntities()) { - entity.tick(); + if (!isFrozen()) { + for (Tickable entity : entityCache.getTickableEntities()) { + entity.tick(); + } } - if (armAnimationTicks >= 0) { // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the // player's effect status, but the animation can cut short if the duration suddenly decreases @@ -1316,7 +1328,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ public void useItem(Hand hand) { sendDownstreamGamePacket(new ServerboundUseItemPacket( - hand, worldCache.nextPredictionSequence(), playerEntity.getYaw(), playerEntity.getPitch())); + hand, worldCache.nextPredictionSequence(), playerEntity.getYaw(), playerEntity.getPitch())); } /** @@ -1365,7 +1377,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private boolean disableBlocking() { if (playerEntity.getFlag(EntityFlag.BLOCKING)) { ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, - Vector3i.ZERO, Direction.DOWN, 0); + Vector3i.ZERO, Direction.DOWN, 0); sendDownstreamGamePacket(releaseItemPacket); playerEntity.setFlag(EntityFlag.BLOCKING, false); return true; @@ -1375,7 +1387,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public void requestOffhandSwap() { ServerboundPlayerActionPacket swapHandsPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, Vector3i.ZERO, - Direction.DOWN, 0); + Direction.DOWN, 0); sendDownstreamGamePacket(swapHandsPacket); } @@ -1584,7 +1596,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { unconfirmedTeleport.resetUnconfirmedFor(); geyser.getLogger().debug("Resending teleport " + unconfirmedTeleport.getTeleportConfirmId()); getPlayerEntity().moveAbsolute(Vector3f.from(unconfirmedTeleport.getX(), unconfirmedTeleport.getY(), unconfirmedTeleport.getZ()), - unconfirmedTeleport.getYaw(), unconfirmedTeleport.getPitch(), playerEntity.isOnGround(), true); + unconfirmedTeleport.getYaw(), unconfirmedTeleport.getPitch(), playerEntity.isOnGround(), true); } } @@ -1711,7 +1723,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { * Send a gamerule value to the client * * @param gameRule The gamerule to send - * @param value The value of the gamerule + * @param value The value of the gamerule */ public void sendGameRule(String gameRule, Object value) { GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket(); @@ -1850,8 +1862,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { */ public void sendJavaClientSettings() { ServerboundClientInformationPacket clientSettingsPacket = new ServerboundClientInformationPacket(locale(), - getRenderDistance(), ChatVisibility.FULL, true, SKIN_PARTS, - HandPreference.RIGHT_HAND, false, true); + getRenderDistance(), ChatVisibility.FULL, true, SKIN_PARTS, + HandPreference.RIGHT_HAND, false, true); sendDownstreamPacket(clientSettingsPacket); } @@ -1904,8 +1916,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { return switch (pose) { case SNEAKING -> 1.27f; case SWIMMING, - FALL_FLYING, // Elytra - SPIN_ATTACK -> 0.4f; // Trident spin attack + FALL_FLYING, // Elytra + SPIN_ATTACK -> 0.4f; // Trident spin attack case SLEEPING -> 0.2f; default -> EntityDefinitions.PLAYER.offset(); }; @@ -1923,7 +1935,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { @Override public UUID javaUuid() { - return playerEntity != null ? playerEntity.getUuid() : null ; + return playerEntity != null ? playerEntity.getUuid() : null; } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStateTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStateTranslator.java new file mode 100644 index 000000000..4e547ef59 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStateTranslator.java @@ -0,0 +1,40 @@ +/* + * 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.java; + +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.packet.ingame.clientbound.ClientboundTickingStatePacket; + +@Translator(packet = ClientboundTickingStatePacket.class) +public class JavaTickingStateTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundTickingStatePacket packet) { + session.updateTickData(packet.getTickRate(), packet.isFrozen()); + } +}