Proper tick rate handling

This commit is contained in:
Ethan 2024-07-12 20:31:02 +08:00
parent 2a6025f3fc
commit 3d957e35b8
5 changed files with 103 additions and 46 deletions

View file

@ -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;
}

View file

@ -422,3 +422,4 @@ public class LivingEntity extends Entity {
return type.getAttribute((float) AttributeUtils.calculateValue(javaAttribute));
}
}

View file

@ -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() {

View file

@ -550,7 +550,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
/**
* Stores cookies sent by the Java server.
*/
@Setter @Getter
@Setter
@Getter
private Map<String, byte[]> 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

View file

@ -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<ClientboundTickingStatePacket> {
@Override
public void translate(GeyserSession session, ClientboundTickingStatePacket packet) {
session.updateTickData(packet.getTickRate(), packet.isFrozen());
}
}