From 8b232d7900c44d43d78a28cfc2cbd6aa558a12a9 Mon Sep 17 00:00:00 2001 From: LetsGoAway <68365423+letsgoawaydev@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:58:04 +0800 Subject: [PATCH] Handle ClientboundTickingStatePacket correctly and fix Throwable Scales (#4850) * Proper tick rate handling * Fix frozen variable getter * Fix formatting i think third attempt * Formatting fix attempt 5 fsdiofhsdioufhvuisdhviuo9ds * Fix stuff, also fixed the sizing of throwables as they were to big * Move update ticking state * Update core/src/main/java/org/geysermc/geyser/session/GeyserSession.java Co-authored-by: rtm516 * Fixes for spaces and documentation * Missed a space * wait now ive fixed it * Fix languages * try again to fix languages * Fix Java doc comments for tickable interface * Fix javadoc comment in Geyser Session * fix comment * fix some tick rate stuffs * Fix build fail * fix some stuff * merge * test * Update languages * Update mappings * delete broken stuff * Fix cooldown * fix cooldowns * Update core/src/main/java/org/geysermc/geyser/util/CooldownUtils.java Co-authored-by: chris * Update BoatEntity.java * Update GeyserSession.java * fix some stuff * Update CooldownUtils.java * fix some accidental formatting issues * Fix missing inport * Update GeyserSession.java * Update core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java Co-authored-by: chris * Update core/src/main/java/org/geysermc/geyser/entity/type/ThrowableItemEntity.java Co-authored-by: chris * Fix missing import --------- Co-authored-by: rtm516 Co-authored-by: chris --- .../geyser/entity/EntityDefinitions.java | 5 +- .../geyser/entity/type/BoatEntity.java | 2 +- .../geyser/entity/type/EnderEyeEntity.java | 48 ++++++++ .../entity/type/ThrowableItemEntity.java | 55 +++++---- .../geysermc/geyser/entity/type/Tickable.java | 17 ++- .../geyser/session/GeyserSession.java | 114 +++++++++++------- .../geyser/session/cache/WorldCache.java | 10 +- .../BedrockMobEquipmentTranslator.java | 2 +- .../java/JavaTickingStateTranslator.java | 40 ++++++ .../java/JavaTickingStepTranslator.java | 40 ++++++ .../java/level/JavaCooldownTranslator.java | 2 +- .../JavaSetTitlesAnimationTranslator.java | 10 +- .../geysermc/geyser/util/CooldownUtils.java | 17 ++- 13 files changed, 278 insertions(+), 84 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/entity/type/EnderEyeEntity.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStateTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStepTranslator.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java index 47b97c934..b2922a570 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java +++ b/core/src/main/java/org/geysermc/geyser/entity/EntityDefinitions.java @@ -38,6 +38,7 @@ import org.geysermc.geyser.entity.type.ChestBoatEntity; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.DisplayBaseEntity; import org.geysermc.geyser.entity.type.EnderCrystalEntity; +import org.geysermc.geyser.entity.type.EnderEyeEntity; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.EvokerFangsEntity; import org.geysermc.geyser.entity.type.ExpOrbEntity; @@ -197,7 +198,7 @@ public final class EntityDefinitions { public static final EntityDefinition EVOKER_FANGS; public static final EntityDefinition EXPERIENCE_BOTTLE; public static final EntityDefinition EXPERIENCE_ORB; - public static final EntityDefinition EYE_OF_ENDER; + public static final EntityDefinition EYE_OF_ENDER; public static final EntityDefinition FALLING_BLOCK; public static final EntityDefinition FIREBALL; public static final EntityDefinition FIREWORK_ROCKET; @@ -345,7 +346,7 @@ public final class EntityDefinitions { .height(0.8f).width(0.5f) .identifier("minecraft:evocation_fang") .build(); - EYE_OF_ENDER = EntityDefinition.inherited(Entity::new, entityBase) + EYE_OF_ENDER = EntityDefinition.inherited(EnderEyeEntity::new, entityBase) .type(EntityType.EYE_OF_ENDER) .heightAndWidth(0.25f) .identifier("minecraft:eye_of_ender_signal") 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 86accea17..cae4dcd11 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 @@ -189,7 +189,7 @@ public class BoatEntity extends Entity implements Leashable, Tickable { session.sendDownstreamGamePacket(steerPacket); return; } - 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/EnderEyeEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/EnderEyeEntity.java new file mode 100644 index 000000000..cc5a58f21 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/entity/type/EnderEyeEntity.java @@ -0,0 +1,48 @@ +/* + * 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.entity.type; + + +import org.cloudburstmc.math.vector.Vector3f; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; +import org.geysermc.geyser.session.GeyserSession; + +import java.util.UUID; + +public class EnderEyeEntity extends Entity { + public EnderEyeEntity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) { + super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); + } + + @Override + protected void initializeMetadata() { + super.initializeMetadata(); + // Correct sizing + dirtyMetadata.put(EntityDataTypes.SCALE, 0.5f); + } +} 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..fbbe2de50 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,14 @@ 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.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.geyser.entity.EntityDefinition; +import org.geysermc.geyser.entity.EntityDefinitions; 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; @@ -39,7 +41,7 @@ import java.util.UUID; */ public class ThrowableItemEntity extends ThrowableEntity { /** - * Number of ticks since the entity was spawned by the Java server + * Number of draw ticks since the entity was spawned by the Java server */ private int age; private boolean invisible; @@ -48,29 +50,38 @@ public class ThrowableItemEntity extends ThrowableEntity { super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); setFlag(EntityFlag.INVISIBLE, true); invisible = false; - } - - private void checkVisibility() { - if (invisible != getFlag(EntityFlag.INVISIBLE)) { - if (!invisible) { - Vector3f playerPos = session.getPlayerEntity().getPosition(); - // Prevent projectiles from blocking the player's screen - if (age >= 4 || position.distanceSquared(playerPos) > 16) { - setFlag(EntityFlag.INVISIBLE, false); - updateBedrockMetadata(); - } - } else { - setFlag(EntityFlag.INVISIBLE, true); - updateBedrockMetadata(); - } - } - age++; + age = 0; } @Override - public void tick() { + protected void initializeMetadata() { + super.initializeMetadata(); + // Correct sizing + dirtyMetadata.put(EntityDataTypes.SCALE, 0.5f); + } + + private void checkVisibility() { + age++; + + // Prevent projectiles from blocking the player's screen + if (session.isTickingFrozen()) { + // This may seem odd, but it matches java edition + Vector3f playerPos = session.getPlayerEntity().getPosition().down(EntityDefinitions.PLAYER.offset()); + setInvisible(playerPos.distanceSquared(position.add(0, definition.offset(), 0)) < 12.25); + } else { + setInvisible(age < 2); + } + + if (invisible != getFlag(EntityFlag.INVISIBLE)) { + setFlag(EntityFlag.INVISIBLE, invisible); + updateBedrockMetadata(); + } + } + + @Override + public void drawTick() { checkVisibility(); - super.tick(); + super.drawTick(); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java b/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java index 06bf45b3d..f61ff355f 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Tickable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-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 @@ -26,8 +26,21 @@ package org.geysermc.geyser.entity.type; /** - * Implemented onto anything that should have code ran every Minecraft tick - 50 milliseconds. + * Implemented onto anything that should have code ran every Minecraft tick. + * By default, the Java server runs at 20 TPS, 50 milliseconds for each tick. */ public interface Tickable { + /** + * This function gets called every tick at all times, even when the server requests that + * the game should be frozen. This should be used for updating things that are always + * client side updated on Java, regardless of if the server is frozen or not. + */ + default void drawTick() { + } + + /** + * This function gets called every game tick as long as the + * game tick loop isn't frozen. + */ void tick(); } 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 b4a8e6203..d9e99b3b8 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-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 @@ -183,6 +183,7 @@ import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.LoginEncryptionUtils; +import org.geysermc.geyser.util.MathUtils; import org.geysermc.geyser.util.MinecraftAuthLogger; import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.network.BuiltinFlags; @@ -600,7 +601,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private boolean advancedTooltips = false; /** - * The thread that will run every 50 milliseconds - one Minecraft tick. + * The thread that will run every game tick. */ private ScheduledFuture tickThread = null; @@ -644,7 +645,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { /** * Stores cookies sent by the Java server. */ - @Setter @Getter + @Setter private Map cookies = new Object2ObjectOpenHashMap<>(); private final GeyserCameraData cameraData; @@ -653,6 +654,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { private MinecraftProtocol protocol; + private int nanosecondsPerTick = 50000000; + private float millisecondsPerTick = 50.0f; + private boolean tickingFrozen = false; + /** + * The amount of ticks requested by the server that the game should proceed with, even if the game tick loop is frozen. + */ + @Setter + private int stepTicks = 0; + + public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) { this.geyser = geyser; this.upstream = new UpstreamSession(bedrockServerSession); @@ -895,38 +906,38 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } task.cleanup(); // player is online -> remove pending authentication immediately return task.getAuthentication().handle((result, ex) -> { - if (ex != null) { - geyser.getLogger().error("Failed to log in with Microsoft code!", ex); - if (ex instanceof CompletionException ce - && ce.getCause() instanceof MinecraftRequestException mre - && mre.getResponse().getStatusCode() == 404) { - // Player is trying to join with a Microsoft account that doesn't have Java Edition purchased - disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", locale())); - } else { - disconnect(ex.toString()); - } - return false; - } + if (ex != null) { + geyser.getLogger().error("Failed to log in with Microsoft code!", ex); + if (ex instanceof CompletionException ce + && ce.getCause() instanceof MinecraftRequestException mre + && mre.getResponse().getStatusCode() == 404) { + // Player is trying to join with a Microsoft account that doesn't have Java Edition purchased + disconnect(GeyserLocale.getPlayerLocaleString("geyser.network.remote.invalid_account", locale())); + } else { + disconnect(ex.toString()); + } + return false; + } - StepMCProfile.MCProfile mcProfile = result.session().getMcProfile(); - StepMCToken.MCToken mcToken = mcProfile.getMcToken(); + StepMCProfile.MCProfile mcProfile = result.session().getMcProfile(); + StepMCToken.MCToken mcToken = mcProfile.getMcToken(); - this.protocol = new MinecraftProtocol( - new GameProfile(mcProfile.getId(), mcProfile.getName()), - mcToken.getAccessToken() - ); + this.protocol = new MinecraftProtocol( + new GameProfile(mcProfile.getId(), mcProfile.getName()), + mcToken.getAccessToken() + ); - try { - connectDownstream(); - } catch (Throwable t) { - t.printStackTrace(); - return false; - } + try { + connectDownstream(); + } catch (Throwable t) { + t.printStackTrace(); + return false; + } - // Save our auth chain for later use - geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session()))); - return true; - }).getNow(false); + // Save our auth chain for later use + geyser.saveAuthChain(bedrockUsername(), GSON.toJson(result.step().toJson(result.session()))); + return true; + }).getNow(false); } /** @@ -947,14 +958,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE; // Start ticking - tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); + tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, nanosecondsPerTick, nanosecondsPerTick, TimeUnit.NANOSECONDS); TcpSession downstream; 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, tickEventLoop); + this.protocol, this.tickEventLoop); this.downstream = new DownstreamSession(downstream); } else { downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), "0.0.0.0", 0, this.protocol, null, tickEventLoop); @@ -1077,11 +1088,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { // Download and load the language for the player MinecraftLocale.downloadAndLoadLocale(locale); - -// if (sentSpawnPacket && !GameProtocol.isPre1_21_2(GeyserSession.this)) { -// // Possible form to close. -// upstream.sendPacket(new ClientboundCloseFormPacket()); -// } } @Override @@ -1243,6 +1249,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { }, duration, timeUnit); } + public void updateTickingState(float tickRate, boolean frozen) { + tickThread.cancel(false); + + this.tickingFrozen = frozen; + + tickRate = MathUtils.clamp(tickRate, 1.0f, 10000.0f); + + millisecondsPerTick = 1000.0f / tickRate; + + nanosecondsPerTick = MathUtils.ceil(1000000000.0f / tickRate); + tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, nanosecondsPerTick, nanosecondsPerTick, TimeUnit.NANOSECONDS); + } + private void executeRunnable(Runnable runnable) { try { runnable.run(); @@ -1251,10 +1270,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { } catch (Throwable e) { geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", e); } + } /** - * Called every 50 milliseconds - one Minecraft tick. + * Called every Minecraft tick. */ protected void tick() { try { @@ -1280,13 +1300,21 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { isInWorldBorderWarningArea = false; } + boolean gameShouldUpdate = !tickingFrozen || stepTicks > 0; + if (stepTicks > 0) { + --stepTicks; + } + Entity vehicle = playerEntity.getVehicle(); if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) { clientVehicle.getVehicleComponent().tickVehicle(); } for (Tickable entity : entityCache.getTickableEntities()) { - entity.tick(); + entity.drawTick(); + if (gameShouldUpdate) { + entity.tick(); + } } if (armAnimationTicks >= 0) { @@ -1424,7 +1452,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource { public void setClientData(BedrockClientData data) { this.clientData = data; this.inputCache.setInputMode( - org.cloudburstmc.protocol.bedrock.data.InputMode.values()[data.getCurrentInputMode().ordinal()]); + org.cloudburstmc.protocol.bedrock.data.InputMode.values()[data.getCurrentInputMode().ordinal()]); } /** @@ -1844,7 +1872,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(); @@ -2110,7 +2138,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/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 609ac3f3b..eb57e0214 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -123,9 +123,13 @@ public final class WorldCache { SetTitlePacket titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setText(""); - titlePacket.setFadeInTime(trueTitleFadeInTime); - titlePacket.setStayTime(trueTitleStayTime); - titlePacket.setFadeOutTime(trueTitleFadeOutTime); + + // We need a tick rate multiplier as otherwise the timings are incorrect on different tick rates because + // bedrock can only run at 20 TPS (50ms = 1 tick) + int tickrateMultiplier = Math.round(session.getMillisecondsPerTick()) / 50; + titlePacket.setFadeInTime(trueTitleFadeInTime * tickrateMultiplier); + titlePacket.setStayTime(trueTitleStayTime * tickrateMultiplier); + titlePacket.setFadeOutTime(trueTitleFadeOutTime * tickrateMultiplier); titlePacket.setPlatformOnlineId(""); titlePacket.setXuid(""); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java index 35ad942d0..64681723e 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockMobEquipmentTranslator.java @@ -66,7 +66,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator session.useItem(Hand.MAIN_HAND), - 50, TimeUnit.MILLISECONDS); + session.getNanosecondsPerTick(), TimeUnit.NANOSECONDS); } if (oldItem.getJavaId() != newItem.getJavaId()) { 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..85d4974cf --- /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.updateTickingState(packet.getTickRate(), packet.isFrozen()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStepTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStepTranslator.java new file mode 100644 index 000000000..f898b762a --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaTickingStepTranslator.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.ClientboundTickingStepPacket; + +@Translator(packet = ClientboundTickingStepPacket.class) +public class JavaTickingStepTranslator extends PacketTranslator { + + @Override + public void translate(GeyserSession session, ClientboundTickingStepPacket packet) { + session.setStepTicks(packet.getTickSteps()); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java index 2b14f015f..4097f5b78 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java @@ -59,7 +59,7 @@ public class JavaCooldownTranslator extends PacketTranslator 20) return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used + if (session.getAttackSpeed() == 0.0 || session.getAttackSpeed() > 20) { + return; // 0.0 usually happens on login and causes issues with visuals; anything above 20 means a plugin like OldCombatMechanics is being used + } // Set the times to stay a bit with no fade in nor out SetTitlePacket titlePacket = new SetTitlePacket(); titlePacket.setType(SetTitlePacket.Type.TIMES); @@ -83,6 +87,7 @@ public class CooldownUtils { /** * Keeps updating the cooldown until the bar is complete. + * * @param session GeyserSession * @param sessionPreference The type of cooldown the client prefers * @param lastHitTime The time of the last hit. Used to gauge how long the cooldown is taking. @@ -102,7 +107,7 @@ public class CooldownUtils { session.sendUpstreamPacket(titlePacket); if (hasCooldown(session)) { session.scheduleInEventLoop(() -> - computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + computeCooldown(session, sessionPreference, lastHitTime), (long) restrain(session.getMillisecondsPerTick(), 50), TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); removeTitlePacket.setType(SetTitlePacket.Type.CLEAR); @@ -115,8 +120,9 @@ public class CooldownUtils { private static boolean hasCooldown(GeyserSession session) { long time = System.currentTimeMillis() - session.getLastHitTime(); - double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1.5); - return cooldown < 1.1; + double tickrateMultiplier = Math.max(session.getMillisecondsPerTick() / 50, 1.0); + double cooldown = restrain(((double) time) * session.getAttackSpeed() / (tickrateMultiplier * 1000.0), 1.0); + return cooldown < 1.0; } @@ -128,7 +134,8 @@ public class CooldownUtils { private static String getTitle(GeyserSession session) { long time = System.currentTimeMillis() - session.getLastHitTime(); - double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1); + double tickrateMultiplier = Math.max(session.getMillisecondsPerTick() / 50, 1.0); + double cooldown = restrain(((double) time) * session.getAttackSpeed() / (tickrateMultiplier * 1000.0), 1.0); int darkGrey = (int) Math.floor(10d * cooldown); int grey = 10 - darkGrey;