diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java index b0d0e1ce6..48afd21fe 100644 --- a/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java +++ b/core/src/main/java/org/geysermc/geyser/command/defaults/OffhandCommand.java @@ -47,7 +47,7 @@ public class OffhandCommand extends GeyserCommand { } ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, Vector3i.ZERO, - Direction.DOWN, session.getNextSequence()); + Direction.DOWN, session.getWorldCache().nextPredictionSequence()); session.sendDownstreamPacket(releaseItemPacket); } 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 93ef62c56..0a916fbf5 100644 --- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java +++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java @@ -401,6 +401,8 @@ public class GeyserSession implements GeyserConnection, CommandSender { */ @Setter private boolean emulatePost1_16Logic = true; + @Setter + private boolean emulatePost1_18Logic = true; /** * The current attack speed of the player. Used for sending proper cooldown timings. @@ -1278,9 +1280,9 @@ public class GeyserSession implements GeyserConnection, CommandSender { ServerboundUseItemPacket useItemPacket; if (playerInventory.getItemInHand().getJavaId() == shield.getJavaId()) { - useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND, getNextSequence()); + useItemPacket = new ServerboundUseItemPacket(Hand.MAIN_HAND, worldCache.nextPredictionSequence()); } else if (playerInventory.getOffhand().getJavaId() == shield.getJavaId()) { - useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND, getNextSequence()); + useItemPacket = new ServerboundUseItemPacket(Hand.OFF_HAND, worldCache.nextPredictionSequence()); } else { // No blocking return false; @@ -1309,7 +1311,7 @@ public class GeyserSession implements GeyserConnection, CommandSender { private boolean disableBlocking() { if (playerEntity.getFlag(EntityFlag.BLOCKING)) { ServerboundPlayerActionPacket releaseItemPacket = new ServerboundPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, - Vector3i.ZERO, Direction.DOWN, getNextSequence()); + Vector3i.ZERO, Direction.DOWN, worldCache.nextPredictionSequence()); sendDownstreamPacket(releaseItemPacket); playerEntity.setFlag(EntityFlag.BLOCKING, false); return true; @@ -1687,10 +1689,6 @@ public class GeyserSession implements GeyserConnection, CommandSender { sendDownstreamPacket(clientSettingsPacket); } - public int getNextSequence() { - return 0; - } - /** * Used for updating statistic values since we only get changes from the server * diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index d46a39616..7db01e655 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -28,6 +28,7 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundUpdateTagsPacket; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; +import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.BlockMapping; import org.geysermc.geyser.registry.type.ItemMapping; @@ -82,6 +83,15 @@ public class TagCache { this.requiresIronTool = IntList.of(blockTags.get("minecraft:needs_iron_tool")); this.requiresDiamondTool = IntList.of(blockTags.get("minecraft:needs_diamond_tool")); + // Hack btw + GeyserLogger logger = session.getGeyser().getLogger(); + int[] convertableToMud = blockTags.get("minecraft:convertable_to_mud"); + boolean emulatePost1_18Logic = convertableToMud != null && convertableToMud.length != 0; + session.setEmulatePost1_18Logic(emulatePost1_18Logic); + if (logger.isDebug()) { + logger.debug("Emulating post 1.18 block predication logic for " + session.name() + "? " + emulatePost1_18Logic); + } + Map itemTags = packet.getTags().get("minecraft:item"); this.axolotlTemptItems = IntList.of(itemTags.get("minecraft:axolotl_tempt_items")); this.fishes = IntList.of(itemTags.get("minecraft:fishes")); @@ -93,8 +103,8 @@ public class TagCache { // Hack btw boolean emulatePost1_14Logic = itemTags.get("minecraft:signs").length > 1; session.setEmulatePost1_14Logic(emulatePost1_14Logic); - if (session.getGeyser().getLogger().isDebug()) { - session.getGeyser().getLogger().debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic); + if (logger.isDebug()) { + logger.debug("Emulating post 1.14 villager logic for " + session.name() + "? " + emulatePost1_14Logic); } } 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 17679ad3e..239f5c865 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 @@ -26,12 +26,18 @@ package org.geysermc.geyser.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.ChunkUtils; + +import java.util.Iterator; +import java.util.Map; public final class WorldCache { private final GeyserSession session; @@ -51,6 +57,9 @@ public final class WorldCache { private int trueTitleStayTime; private int trueTitleFadeOutTime; + private int currentSequence; + private final Map unverifiedPredictions = new Object2ObjectOpenHashMap<>(1); + public WorldCache(GeyserSession session) { this.session = session; this.scoreboard = new Scoreboard(session); @@ -121,4 +130,75 @@ public final class WorldCache { forceSyncCorrectTitleTimes(); } } + + /* Code to support the prediction structure introduced in Java Edition 1.19.0 + Blocks can be rolled back if invalid, but this requires some client-side information storage. */ + + public int nextPredictionSequence() { + return ++currentSequence; + } + + /** + * Stores a record of a block at a certain position to rollback in the event it is incorrect. + */ + public void addServerCorrectBlockState(Vector3i position, int blockState) { + if (session.isEmulatePost1_18Logic()) { + // Cheap hack + // On non-Bukkit platforms, ViaVersion will always confirm the sequence before the block is updated, + // meaning we'd send two block updates after (ChunkUtils.updateBlockClientSide in endPredictionsUpTo + // and the packet updating from the client) + this.unverifiedPredictions.compute(position, ($, serverVerifiedState) -> serverVerifiedState == null + ? new ServerVerifiedState(currentSequence, blockState) : serverVerifiedState.setData(currentSequence, blockState)); + } + } + + public void updateServerCorrectBlockState(Vector3i position) { + if (this.unverifiedPredictions.isEmpty()) { + return; + } + + this.unverifiedPredictions.remove(position); + } + + public void endPredictionsUpTo(int sequence) { + if (this.unverifiedPredictions.isEmpty()) { + return; + } + + Iterator> it = this.unverifiedPredictions.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + ServerVerifiedState serverVerifiedState = entry.getValue(); + if (serverVerifiedState.sequence <= sequence) { + // This block may be out of sync with the server + // In 1.19.0 Java, you can verify this by trying to mine in spawn protection + ChunkUtils.updateBlockClientSide(session, serverVerifiedState.blockState, entry.getKey()); + it.remove(); + } + } + } + + private static class ServerVerifiedState { + private int sequence; + private int blockState; + + ServerVerifiedState(int sequence, int blockState) { + this.sequence = sequence; + this.blockState = blockState; + } + + ServerVerifiedState setData(int sequence, int blockState) { + this.sequence = sequence; + this.blockState = blockState; + return this; + } + + @Override + public String toString() { + return "ServerVerifiedState{" + + "sequence=" + sequence + + ", blockState=" + blockState + + '}'; + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 1adf123bf..24c046ef2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -38,7 +38,10 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.inventory.*; -import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; +import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMaps; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -126,7 +129,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator legacySlots = packet.getLegacySlots(); @@ -406,12 +409,22 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND, session.getNextSequence())), + session.scheduleInEventLoop(() -> session.sendDownstreamPacket(new ServerboundUseItemPacket(Hand.MAIN_HAND, session.getWorldCache().nextPredictionSequence())), 50, TimeUnit.MILLISECONDS); } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java index dd7a1788c..5001fc2d2 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockActionTranslator.java @@ -41,7 +41,6 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.ItemFrameEntity; import org.geysermc.geyser.entity.type.player.SessionPlayerEntity; -import org.geysermc.geyser.level.block.BlockStateValues; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; @@ -129,7 +128,7 @@ public class BedrockActionTranslator extends PacketTranslator { if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() != EmoteOffhandWorkaroundOption.DISABLED) { // Activate the workaround - we should trigger the offhand now ServerboundPlayerActionPacket swapHandsPacket = new ServerboundPlayerActionPacket(PlayerAction.SWAP_HANDS, Vector3i.ZERO, - Direction.DOWN, session.getNextSequence()); + Direction.DOWN, session.getWorldCache().nextPredictionSequence()); session.sendDownstreamPacket(swapHandsPacket); if (session.getGeyser().getConfig().getEmoteOffhandWorkaround() == EmoteOffhandWorkaroundOption.NO_EMOTES) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockChangedAckTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockChangedAckTranslator.java index 6afb0b3ef..523d0fdc4 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockChangedAckTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaBlockChangedAckTranslator.java @@ -35,6 +35,6 @@ public class JavaBlockChangedAckTranslator extends PacketTranslator