Merge remote-tracking branch 'refs/remotes/upstream/master' into feature/1.21.4

This commit is contained in:
onebeastchris 2024-12-10 20:43:54 +08:00
commit 06a9b2866e
14 changed files with 304 additions and 84 deletions

View file

@ -24,15 +24,19 @@
*/ */
package org.geysermc.geyser.platform.viaproxy; package org.geysermc.geyser.platform.viaproxy;
import io.netty.channel.AbstractChannel;
import net.lenni0451.lambdaevents.EventHandler; import net.lenni0451.lambdaevents.EventHandler;
import net.lenni0451.reflect.stream.RStream;
import net.raphimc.vialegacy.api.LegacyProtocolVersion; import net.raphimc.vialegacy.api.LegacyProtocolVersion;
import net.raphimc.viaproxy.ViaProxy; import net.raphimc.viaproxy.ViaProxy;
import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.ViaProxyPlugin; import net.raphimc.viaproxy.plugins.ViaProxyPlugin;
import net.raphimc.viaproxy.plugins.events.Client2ProxyChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent; import net.raphimc.viaproxy.plugins.events.ConsoleCommandEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStartEvent; import net.raphimc.viaproxy.plugins.events.ProxyStartEvent;
import net.raphimc.viaproxy.plugins.events.ProxyStopEvent; import net.raphimc.viaproxy.plugins.events.ProxyStopEvent;
import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent; import net.raphimc.viaproxy.plugins.events.ShouldVerifyOnlineModeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserBootstrap; import org.geysermc.geyser.GeyserBootstrap;
@ -56,6 +60,7 @@ import org.geysermc.geyser.util.LoopbackUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.UUID; import java.util.UUID;
@ -109,6 +114,27 @@ public class GeyserViaProxyPlugin extends ViaProxyPlugin implements GeyserBootst
} }
} }
@EventHandler
private void onClient2ProxyChannelInitialize(Client2ProxyChannelInitializeEvent event) {
if (event.getType() != ITyped.Type.POST || event.isLegacyPassthrough()) {
return;
}
if (System.getProperty("geyser.viaproxy.disableIpPassthrough") != null) { // Temporary until Configurate branch is merged
return;
}
final GeyserSession session = GeyserImpl.getInstance().onlineConnections().stream()
.filter(c -> c.getDownstream() != null)
.filter(c -> c.getDownstream().getSession().getLocalAddress().equals(event.getChannel().remoteAddress()))
.findAny().orElse(null);
if (session != null) {
final SocketAddress realAddress = session.getSocketAddress();
if (event.getChannel() instanceof AbstractChannel) {
RStream.of(AbstractChannel.class, event.getChannel()).fields().by("remoteAddress").set(realAddress);
}
}
}
@EventHandler @EventHandler
private void onProxyStart(final ProxyStartEvent event) { private void onProxyStart(final ProxyStartEvent event) {
this.onGeyserEnable(); this.onGeyserEnable();

View file

@ -38,6 +38,7 @@ import org.geysermc.geyser.entity.type.ChestBoatEntity;
import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity; import org.geysermc.geyser.entity.type.CommandBlockMinecartEntity;
import org.geysermc.geyser.entity.type.DisplayBaseEntity; import org.geysermc.geyser.entity.type.DisplayBaseEntity;
import org.geysermc.geyser.entity.type.EnderCrystalEntity; 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.Entity;
import org.geysermc.geyser.entity.type.EvokerFangsEntity; import org.geysermc.geyser.entity.type.EvokerFangsEntity;
import org.geysermc.geyser.entity.type.ExpOrbEntity; import org.geysermc.geyser.entity.type.ExpOrbEntity;
@ -199,7 +200,7 @@ public final class EntityDefinitions {
public static final EntityDefinition<EvokerFangsEntity> EVOKER_FANGS; public static final EntityDefinition<EvokerFangsEntity> EVOKER_FANGS;
public static final EntityDefinition<ThrowableItemEntity> EXPERIENCE_BOTTLE; public static final EntityDefinition<ThrowableItemEntity> EXPERIENCE_BOTTLE;
public static final EntityDefinition<ExpOrbEntity> EXPERIENCE_ORB; public static final EntityDefinition<ExpOrbEntity> EXPERIENCE_ORB;
public static final EntityDefinition<Entity> EYE_OF_ENDER; public static final EntityDefinition<EnderEyeEntity> EYE_OF_ENDER;
public static final EntityDefinition<FallingBlockEntity> FALLING_BLOCK; public static final EntityDefinition<FallingBlockEntity> FALLING_BLOCK;
public static final EntityDefinition<FireballEntity> FIREBALL; public static final EntityDefinition<FireballEntity> FIREBALL;
public static final EntityDefinition<FireworkEntity> FIREWORK_ROCKET; public static final EntityDefinition<FireworkEntity> FIREWORK_ROCKET;
@ -349,7 +350,7 @@ public final class EntityDefinitions {
.height(0.8f).width(0.5f) .height(0.8f).width(0.5f)
.identifier("minecraft:evocation_fang") .identifier("minecraft:evocation_fang")
.build(); .build();
EYE_OF_ENDER = EntityDefinition.inherited(Entity::new, entityBase) EYE_OF_ENDER = EntityDefinition.inherited(EnderEyeEntity::new, entityBase)
.type(EntityType.EYE_OF_ENDER) .type(EntityType.EYE_OF_ENDER)
.heightAndWidth(0.25f) .heightAndWidth(0.25f)
.identifier("minecraft:eye_of_ender_signal") .identifier("minecraft:eye_of_ender_signal")

View file

@ -195,7 +195,7 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
session.sendDownstreamGamePacket(steerPacket); session.sendDownstreamGamePacket(steerPacket);
return; return;
} }
doTick = !doTick; // Run every 100 ms doTick = !doTick; // Run every other tick
if (!doTick || passengers.isEmpty()) { if (!doTick || passengers.isEmpty()) {
return; return;
} }

View file

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

View file

@ -25,12 +25,14 @@
package org.geysermc.geyser.entity.type; 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.math.vector.Vector3f;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
import org.geysermc.geyser.session.GeyserSession; 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; import java.util.UUID;
@ -39,7 +41,7 @@ import java.util.UUID;
*/ */
public class ThrowableItemEntity extends ThrowableEntity { 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 int age;
private boolean invisible; private boolean invisible;
@ -48,29 +50,38 @@ public class ThrowableItemEntity extends ThrowableEntity {
super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw); super(session, entityId, geyserId, uuid, definition, position, motion, yaw, pitch, headYaw);
setFlag(EntityFlag.INVISIBLE, true); setFlag(EntityFlag.INVISIBLE, true);
invisible = false; invisible = false;
} age = 0;
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++;
} }
@Override @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(); checkVisibility();
super.tick(); super.drawTick();
} }
@Override @Override

View file

@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -26,8 +26,21 @@
package org.geysermc.geyser.entity.type; 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 { 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(); void tick();
} }

View file

@ -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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -182,6 +182,7 @@ import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.EntityUtils;
import org.geysermc.geyser.util.InventoryUtils; import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.geyser.util.LoginEncryptionUtils; import org.geysermc.geyser.util.LoginEncryptionUtils;
import org.geysermc.geyser.util.MathUtils;
import org.geysermc.geyser.util.MinecraftAuthLogger; import org.geysermc.geyser.util.MinecraftAuthLogger;
import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.auth.GameProfile;
import org.geysermc.mcprotocollib.network.BuiltinFlags; import org.geysermc.mcprotocollib.network.BuiltinFlags;
@ -604,7 +605,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private boolean advancedTooltips = false; 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; private ScheduledFuture<?> tickThread = null;
@ -648,7 +649,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
/** /**
* Stores cookies sent by the Java server. * Stores cookies sent by the Java server.
*/ */
@Setter @Getter @Setter
private Map<String, byte[]> cookies = new Object2ObjectOpenHashMap<>(); private Map<String, byte[]> cookies = new Object2ObjectOpenHashMap<>();
private final GeyserCameraData cameraData; private final GeyserCameraData cameraData;
@ -657,6 +658,16 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private MinecraftProtocol protocol; 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) { public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) {
this.geyser = geyser; this.geyser = geyser;
this.upstream = new UpstreamSession(bedrockServerSession); this.upstream = new UpstreamSession(bedrockServerSession);
@ -951,14 +962,14 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE; boolean floodgate = this.remoteServer.authType() == AuthType.FLOODGATE;
// Start ticking // Start ticking
tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); tickThread = tickEventLoop.scheduleAtFixedRate(this::tick, nanosecondsPerTick, nanosecondsPerTick, TimeUnit.NANOSECONDS);
TcpSession downstream; TcpSession downstream;
if (geyser.getBootstrap().getSocketAddress() != null) { if (geyser.getBootstrap().getSocketAddress() != null) {
// We're going to connect through the JVM and not through TCP // We're going to connect through the JVM and not through TCP
downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(), downstream = new LocalSession(this.remoteServer.address(), this.remoteServer.port(),
geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(), geyser.getBootstrap().getSocketAddress(), upstream.getAddress().getAddress().getHostAddress(),
this.protocol, tickEventLoop); this.protocol, this.tickEventLoop);
this.downstream = new DownstreamSession(downstream); this.downstream = new DownstreamSession(downstream);
} else { } else {
downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), "0.0.0.0", 0, this.protocol, null, tickEventLoop); downstream = new TcpClientSession(this.remoteServer.address(), this.remoteServer.port(), "0.0.0.0", 0, this.protocol, null, tickEventLoop);
@ -1081,11 +1092,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
// Download and load the language for the player // Download and load the language for the player
MinecraftLocale.downloadAndLoadLocale(locale); MinecraftLocale.downloadAndLoadLocale(locale);
// if (sentSpawnPacket && !GameProtocol.isPre1_21_2(GeyserSession.this)) {
// // Possible form to close.
// upstream.sendPacket(new ClientboundCloseFormPacket());
// }
} }
@Override @Override
@ -1250,6 +1256,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
}, duration, timeUnit); }, 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) { private void executeRunnable(Runnable runnable) {
try { try {
runnable.run(); runnable.run();
@ -1258,10 +1277,11 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
} catch (Throwable e) { } catch (Throwable e) {
geyser.getLogger().error("Error thrown in " + this.bedrockUsername() + "'s event loop!", 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() { protected void tick() {
try { try {
@ -1287,14 +1307,22 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
isInWorldBorderWarningArea = false; isInWorldBorderWarningArea = false;
} }
boolean gameShouldUpdate = !tickingFrozen || stepTicks > 0;
if (stepTicks > 0) {
--stepTicks;
}
Entity vehicle = playerEntity.getVehicle(); Entity vehicle = playerEntity.getVehicle();
if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) { if (vehicle instanceof ClientVehicle clientVehicle && vehicle.isValid()) {
clientVehicle.getVehicleComponent().tickVehicle(); clientVehicle.getVehicleComponent().tickVehicle();
} }
for (Tickable entity : entityCache.getTickableEntities()) { for (Tickable entity : entityCache.getTickableEntities()) {
entity.drawTick();
if (gameShouldUpdate) {
entity.tick(); entity.tick();
} }
}
if (armAnimationTicks >= 0) { if (armAnimationTicks >= 0) {
// As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the // As of 1.18.2 Java Edition, it appears that the swing time is dynamically updated depending on the

View file

@ -123,9 +123,13 @@ public final class WorldCache {
SetTitlePacket titlePacket = new SetTitlePacket(); SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setText(""); titlePacket.setText("");
titlePacket.setFadeInTime(trueTitleFadeInTime);
titlePacket.setStayTime(trueTitleStayTime); // We need a tick rate multiplier as otherwise the timings are incorrect on different tick rates because
titlePacket.setFadeOutTime(trueTitleFadeOutTime); // 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.setPlatformOnlineId("");
titlePacket.setXuid(""); titlePacket.setXuid("");

View file

@ -66,7 +66,7 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator<MobEquipment
// (No need to send a release item packet - Java doesn't do this when swapping items) // (No need to send a release item packet - Java doesn't do this when swapping items)
// Required to do it a tick later or else it doesn't register // Required to do it a tick later or else it doesn't register
session.scheduleInEventLoop(() -> session.useItem(Hand.MAIN_HAND), session.scheduleInEventLoop(() -> session.useItem(Hand.MAIN_HAND),
50, TimeUnit.MILLISECONDS); session.getNanosecondsPerTick(), TimeUnit.NANOSECONDS);
} }
if (oldItem.getJavaId() != newItem.getJavaId()) { if (oldItem.getJavaId() != newItem.getJavaId()) {

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.updateTickingState(packet.getTickRate(), packet.isFrozen());
}
}

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.ClientboundTickingStepPacket;
@Translator(packet = ClientboundTickingStepPacket.class)
public class JavaTickingStepTranslator extends PacketTranslator<ClientboundTickingStepPacket> {
@Override
public void translate(GeyserSession session, ClientboundTickingStepPacket packet) {
session.setStepTicks(packet.getTickSteps());
}
}

View file

@ -59,7 +59,7 @@ public class JavaCooldownTranslator extends PacketTranslator<ClientboundCooldown
if (cooldownCategory != null) { if (cooldownCategory != null) {
PlayerStartItemCooldownPacket bedrockPacket = new PlayerStartItemCooldownPacket(); PlayerStartItemCooldownPacket bedrockPacket = new PlayerStartItemCooldownPacket();
bedrockPacket.setItemCategory(cooldownCategory); bedrockPacket.setItemCategory(cooldownCategory);
bedrockPacket.setCooldownDuration(packet.getCooldownTicks()); bedrockPacket.setCooldownDuration(Math.round(packet.getCooldownTicks() * (session.getMillisecondsPerTick() / 50)));
session.sendUpstreamPacket(bedrockPacket); session.sendUpstreamPacket(bedrockPacket);
} }

View file

@ -40,13 +40,15 @@ public class JavaSetTitlesAnimationTranslator extends PacketTranslator<Clientbou
int stayTime = packet.getStay(); int stayTime = packet.getStay();
int fadeOutTime = packet.getFadeOut(); int fadeOutTime = packet.getFadeOut();
session.getWorldCache().setTitleTimes(fadeInTime, stayTime, fadeOutTime); session.getWorldCache().setTitleTimes(fadeInTime, stayTime, fadeOutTime);
// 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;
SetTitlePacket titlePacket = new SetTitlePacket(); SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setType(SetTitlePacket.Type.TIMES);
titlePacket.setText(""); titlePacket.setText("");
titlePacket.setFadeInTime(fadeInTime); titlePacket.setFadeInTime(fadeInTime * tickrateMultiplier);
titlePacket.setFadeOutTime(fadeOutTime); titlePacket.setFadeOutTime(fadeOutTime * tickrateMultiplier);
titlePacket.setStayTime(stayTime); titlePacket.setStayTime(stayTime * tickrateMultiplier);
titlePacket.setXuid(""); titlePacket.setXuid("");
titlePacket.setPlatformOnlineId(""); titlePacket.setPlatformOnlineId("");
session.sendUpstreamPacket(titlePacket); session.sendUpstreamPacket(titlePacket);

View file

@ -26,6 +26,7 @@
package org.geysermc.geyser.util; package org.geysermc.geyser.util;
import lombok.Getter; import lombok.Getter;
import org.cloudburstmc.math.GenericMath;
import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.PreferencesCache; import org.geysermc.geyser.session.cache.PreferencesCache;
@ -50,6 +51,7 @@ public class CooldownUtils {
/** /**
* Starts sending the fake cooldown to the Bedrock client. If the cooldown is not disabled, the sent type is the cooldownPreference in {@link PreferencesCache} * Starts sending the fake cooldown to the Bedrock client. If the cooldown is not disabled, the sent type is the cooldownPreference in {@link PreferencesCache}
*
* @param session GeyserSession * @param session GeyserSession
*/ */
public static void sendCooldown(GeyserSession session) { public static void sendCooldown(GeyserSession session) {
@ -57,7 +59,9 @@ public class CooldownUtils {
CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference();
if (sessionPreference == CooldownType.DISABLED) return; if (sessionPreference == CooldownType.DISABLED) return;
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 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 // Set the times to stay a bit with no fade in nor out
SetTitlePacket titlePacket = new SetTitlePacket(); SetTitlePacket titlePacket = new SetTitlePacket();
titlePacket.setType(SetTitlePacket.Type.TIMES); titlePacket.setType(SetTitlePacket.Type.TIMES);
@ -83,6 +87,7 @@ public class CooldownUtils {
/** /**
* Keeps updating the cooldown until the bar is complete. * Keeps updating the cooldown until the bar is complete.
*
* @param session GeyserSession * @param session GeyserSession
* @param sessionPreference The type of cooldown the client prefers * @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. * @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); session.sendUpstreamPacket(titlePacket);
if (hasCooldown(session)) { if (hasCooldown(session)) {
session.scheduleInEventLoop(() -> 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 { } else {
SetTitlePacket removeTitlePacket = new SetTitlePacket(); SetTitlePacket removeTitlePacket = new SetTitlePacket();
removeTitlePacket.setType(SetTitlePacket.Type.CLEAR); removeTitlePacket.setType(SetTitlePacket.Type.CLEAR);
@ -115,8 +120,9 @@ public class CooldownUtils {
private static boolean hasCooldown(GeyserSession session) { private static boolean hasCooldown(GeyserSession session) {
long time = System.currentTimeMillis() - session.getLastHitTime(); long time = System.currentTimeMillis() - session.getLastHitTime();
double cooldown = restrain(((double) time) * session.getAttackSpeed() / 1000d, 1.5); double tickrateMultiplier = Math.max(session.getMillisecondsPerTick() / 50, 1.0);
return cooldown < 1.1; 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) { private static String getTitle(GeyserSession session) {
long time = System.currentTimeMillis() - session.getLastHitTime(); 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 darkGrey = (int) Math.floor(10d * cooldown);
int grey = 10 - darkGrey; int grey = 10 - darkGrey;