diff --git a/README.md b/README.md index a51c61f9f..21cdf945e 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Structure block UI ## What can't be fixed -The following things cannot be fixed without changes to Bedrock. As of now, they are not fixable in Geyser. - -- Custom heads in inventories -- Clickable links in chat -- Glowing effect - -Do note that some things require the [GeyserOptionalPack](https://github.com/GeyserMC/Geyser/wiki/GeyserOptionalPack) in order to function, such as custom armor stand poses. +There are a few things Geyser is unable to support due to various differences between Minecraft Bedrock and Java. For a list of these limitations, see the [Current Limitations](https://github.com/GeyserMC/Geyser/wiki/Current-Limitations) page. ## Compiling 1. Clone the repo to your computer diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 5bbff0647..5e15162b8 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -20,7 +20,7 @@ net.md-5 bungeecord-api - 1.16-R0.4-SNAPSHOT + 1.16-R0.5-SNAPSHOT provided @@ -66,8 +66,10 @@ org.geysermc.platform.bungeecord.shaded.jackson - io.netty - org.geysermc.platform.bungeecord.shaded.netty + + io.netty.channel.kqueue + org.geysermc.platform.bungeecord.shaded.io.netty.channel.kqueue org.reflections @@ -98,6 +100,15 @@ com.google.code.gson:* org.yaml:* + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-resolver-dns:* diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index dc9a95d5f..9890e79e7 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -68,10 +68,6 @@ - - io.netty - org.geysermc.platform.spigot.shaded.netty - it.unimi.dsi.fastutil org.geysermc.platform.spigot.shaded.fastutil @@ -109,6 +105,20 @@ com.google.code.gson:* org.yaml:* + + + io.netty:netty-transport-native-epoll:* + io.netty:netty-transport-native-unix-common:* + io.netty:netty-transport-native-kqueue:* + io.netty:netty-handler:* + io.netty:netty-common:* + io.netty:netty-buffer:* + io.netty:netty-resolver:* + io.netty:netty-transport:* + io.netty:netty-codec:* + io.netty:netty-codec-dns:* + io.netty:netty-resolver-dns:* + io.netty:netty-resolver-dns-native-macos:* diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 0d99f89cc..2d3b4954e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -95,6 +95,23 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { ex.printStackTrace(); } + try { + // Required for the Cloudburst Network dependency to initialize. + Class.forName("io.netty.channel.kqueue.KQueue"); + } catch (ClassNotFoundException e) { + // While we could support these older versions, the downside is not having KQueue working at all + // And since there are alternative ways to get Geyser working for these aging platforms, it's not worth it. + getLogger().severe("*********************************************"); + getLogger().severe(""); + getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.unsupported_server.header")); + getLogger().severe(LanguageUtils.getLocaleStringLog("geyser.bootstrap.unsupported_server.message", "1.12.2")); + getLogger().severe(""); + getLogger().severe("*********************************************"); + + Bukkit.getPluginManager().disablePlugin(this); + return; + } + // By default this should be localhost but may need to be changed in some circumstances if (this.geyserConfig.getRemote().getAddress().equalsIgnoreCase("auto")) { geyserConfig.setAutoconfiguredRemote(true); diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 5dadc83c4..3b093285d 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -103,6 +103,7 @@ io.netty:netty-resolver:* io.netty:netty-transport:* io.netty:netty-codec:* + io.netty:netty-codec-haproxy:* org.slf4j:* org.ow2.asm:* diff --git a/connector/pom.xml b/connector/pom.xml index ea5d3c516..1ed01a0c4 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -133,7 +133,7 @@ com.github.steveice10 mcprotocollib - 26201a4 + 8c204eb compile @@ -153,7 +153,7 @@ com.github.GeyserMC PacketLib - b77a427 + 6e5dea9 compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index d1122b65f..52f3680e2 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -31,6 +31,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.network.raknet.RakNetConstants; import com.nukkitx.network.util.EventLoops; import com.nukkitx.protocol.bedrock.BedrockServer; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.kqueue.KQueue; import lombok.Getter; import lombok.Setter; import org.geysermc.common.PlatformType; @@ -211,7 +213,7 @@ public class GeyserConnector { } } - CooldownUtils.setShowCooldown(config.getShowCooldown()); + CooldownUtils.setDefaultShowCooldown(config.getShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); @@ -226,6 +228,19 @@ public class GeyserConnector { EventLoops.commonGroup(), enableProxyProtocol ); + + if (config.isDebugMode()) { + logger.debug("EventLoop type: " + EventLoops.getChannelType()); + if (EventLoops.getChannelType() == EventLoops.ChannelType.NIO) { + if (System.getProperties().contains("disableNativeEventLoop")) { + logger.debug("EventLoop type is NIO because native event loops are disabled."); + } else { + logger.debug("Reason for no Epoll: " + Epoll.unavailabilityCause().toString()); + logger.debug("Reason for no KQueue: " + KQueue.unavailabilityCause().toString()); + } + } + } + bedrockServer.setHandler(new ConnectorServerEventHandler(this)); bedrockServer.bind().whenComplete((avoid, throwable) -> { if (throwable == null) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 15d246fee..9068323e6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -46,10 +46,9 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTelepo import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket; import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.packetlib.BuiltinFlags; -import com.github.steveice10.packetlib.Client; import com.github.steveice10.packetlib.event.session.*; import com.github.steveice10.packetlib.packet.Packet; -import com.github.steveice10.packetlib.tcp.TcpSessionFactory; +import com.github.steveice10.packetlib.tcp.TcpClientSession; import com.nukkitx.math.GenericMath; import com.nukkitx.math.vector.*; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -115,7 +114,7 @@ public class GeyserSession implements CommandSender { private final GeyserConnector connector; private final UpstreamSession upstream; - private Client downstream; + private TcpClientSession downstream; @Setter private AuthData authData; @Setter @@ -141,6 +140,7 @@ public class GeyserSession implements CommandSender { private ChunkCache chunkCache; private EntityCache entityCache; private EntityEffectCache effectCache; + private final PreferencesCache preferencesCache; private final TagCache tagCache; private WorldCache worldCache; private FormCache formCache; @@ -445,6 +445,7 @@ public class GeyserSession implements CommandSender { this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); this.effectCache = new EntityEffectCache(); + this.preferencesCache = new PreferencesCache(this); this.tagCache = new TagCache(); this.worldCache = new WorldCache(this); this.formCache = new FormCache(this); @@ -578,7 +579,7 @@ public class GeyserSession implements CommandSender { authenticationService.setPassword(password); authenticationService.login(); - protocol = new MinecraftProtocol(authenticationService); + protocol = new MinecraftProtocol(authenticationService.getSelectedProfile(), authenticationService.getAccessToken()); } else { protocol = new MinecraftProtocol(username); } @@ -636,7 +637,7 @@ public class GeyserSession implements CommandSender { } try { msaAuthenticationService.login(); - protocol = new MinecraftProtocol(msaAuthenticationService); + protocol = new MinecraftProtocol(msaAuthenticationService.getSelectedProfile(), msaAuthenticationService.getAccessToken()); connectDownstream(); } catch (RequestException e) { @@ -658,17 +659,17 @@ public class GeyserSession implements CommandSender { // Start ticking tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); - downstream = new Client(this.remoteAddress, this.remotePort, protocol, new TcpSessionFactory()); + downstream = new TcpClientSession(this.remoteAddress, this.remotePort, protocol); disableSrvResolving(); if (connector.getConfig().getRemote().isUseProxyProtocol()) { - downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); - downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); + downstream.setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); + downstream.setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } if (connector.getConfig().isForwardPlayerPing()) { // Let Geyser handle sending the keep alive - downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); + downstream.setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); } - downstream.getSession().addListener(new SessionAdapter() { + downstream.addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { //todo move this somewhere else @@ -805,15 +806,15 @@ public class GeyserSession implements CommandSender { if (!daylightCycle) { setDaylightCycle(true); } - downstream.getSession().connect(); + downstream.connect(); connector.addPlayer(this); } public void disconnect(String reason) { if (!closed) { loggedIn = false; - if (downstream != null && downstream.getSession() != null) { - downstream.getSession().disconnect(reason); + if (downstream != null) { + downstream.disconnect(reason); } if (upstream != null && !upstream.isClosed()) { connector.getPlayers().remove(this); @@ -941,7 +942,7 @@ public class GeyserSession implements CommandSender { * Will be overwritten for GeyserConnect. */ protected void disableSrvResolving() { - this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); + this.downstream.setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); } @Override @@ -1197,8 +1198,8 @@ public class GeyserSession implements CommandSender { * @param packet the java edition packet from MCProtocolLib */ public void sendDownstreamPacket(Packet packet) { - if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { - downstream.getSession().send(packet); + if (downstream != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { + downstream.send(packet); } else { connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); } @@ -1213,7 +1214,7 @@ public class GeyserSession implements CommandSender { public void setReducedDebugInfo(boolean value) { reducedDebugInfo = value; // Set the showCoordinates data. This is done because updateShowCoordinates() uses this gamerule as a variable. - getWorldCache().updateShowCoordinates(); + preferencesCache.updateShowCoordinates(); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java index c82645dbf..cb3737895 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/BookEditCache.java @@ -68,7 +68,7 @@ public class BookEditCache { packet = null; return; } - session.getDownstream().getSession().send(packet); + session.sendDownstreamPacket(packet); packet = null; lastBookUpdate = System.currentTimeMillis(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java new file mode 100644 index 000000000..d477066c2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/PreferencesCache.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2021 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.connector.network.session.cache; + +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.configuration.GeyserConfiguration; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.CooldownUtils; + +@Getter +public class PreferencesCache { + private final GeyserSession session; + + /** + * True if the client prefers being shown their coordinates, regardless if they're being shown or not. + * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. + */ + @Setter + private boolean prefersShowCoordinates = true; + /** + * If the client's preference will be ignored, this will return false. + */ + private boolean allowShowCoordinates; + + /** + * Which CooldownType the client prefers. Initially set to {@link CooldownUtils#getDefaultShowCooldown()}. + */ + @Setter + private CooldownUtils.CooldownType cooldownPreference = CooldownUtils.getDefaultShowCooldown(); + + public PreferencesCache(GeyserSession session) { + this.session = session; + } + + /** + * Tell the client to hide or show the coordinates. + * + * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
+ *
+ * {@link GeyserSession#reducedDebugInfo} is enabled + * {@link GeyserConfiguration#isShowCoordinates()} is disabled + */ + public void updateShowCoordinates() { + allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); + session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java index 84678c211..4a2939621 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/WorldCache.java @@ -28,7 +28,6 @@ package org.geysermc.connector.network.session.cache; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import lombok.Getter; import lombok.Setter; -import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Objective; import org.geysermc.connector.scoreboard.Scoreboard; @@ -40,13 +39,6 @@ public class WorldCache { @Setter private Difficulty difficulty = Difficulty.EASY; - /** - * True if the client prefers being shown their coordinates, regardless if they're being shown or not. - * This will be true everytime the client joins the server because neither the client nor server store the preference permanently. - */ - @Setter - private boolean prefersShowCoordinates = true; - private Scoreboard scoreboard; private final ScoreboardUpdater scoreboardUpdater; @@ -71,17 +63,4 @@ public class WorldCache { int pps = scoreboardUpdater.getPacketsPerSecond(); return Math.max(pps, pendingPps); } - - /** - * Tell the client to hide or show the coordinates. - * - * If {@link #prefersShowCoordinates} is true, coordinates will be shown, unless either of the following conditions apply:
- *
- * {@link GeyserSession#reducedDebugInfo} is enabled - * {@link GeyserConfiguration#isShowCoordinates()} is disabled - */ - public void updateShowCoordinates() { - boolean allowShowCoordinates = !session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates(); - session.sendGameRule("showcoordinates", allowShowCoordinates && prefersShowCoordinates); - } } \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 2358fa278..6378c0ba9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -39,10 +39,7 @@ 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.entity.EntityFlags; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; +import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; @@ -241,9 +238,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator 0) { InventoryActionData actionData = packet.getActions().get(0); - if (actionData.getSlot() == 6 && actionData.getToItem().getId() != 0) { + LegacySetItemSlotData slotData = packet.getLegacySlots().get(0); + if (slotData.getContainerId() == 6 && actionData.getToItem().getId() != 0) { // The player is trying to swap out an armor piece that already has an item in it // Java Edition does not allow this; let's revert it session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java index 0b5c2bdd3..43675ebfd 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/CooldownUtils.java @@ -28,6 +28,7 @@ package org.geysermc.connector.utils; import com.nukkitx.protocol.bedrock.packet.SetTitlePacket; import lombok.Getter; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.session.cache.PreferencesCache; import java.util.concurrent.TimeUnit; @@ -36,18 +37,25 @@ import java.util.concurrent.TimeUnit; * Much of the work here is from the wonderful folks from ViaRewind: https://github.com/ViaVersion/ViaRewind */ public class CooldownUtils { - private static CooldownType SHOW_COOLDOWN; + private static CooldownType DEFAULT_SHOW_COOLDOWN; - public static void setShowCooldown(String showCooldown) { - SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + public static void setDefaultShowCooldown(String showCooldown) { + DEFAULT_SHOW_COOLDOWN = CooldownType.getByName(showCooldown); + } + + public static CooldownType getDefaultShowCooldown() { + return DEFAULT_SHOW_COOLDOWN; } /** - * Starts sending the fake cooldown to the Bedrock client. + * 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 */ public static void sendCooldown(GeyserSession session) { - if (SHOW_COOLDOWN == CooldownType.DISABLED) return; + if (DEFAULT_SHOW_COOLDOWN == CooldownType.DISABLED) return; + CooldownType sessionPreference = session.getPreferencesCache().getCooldownPreference(); + 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 // Needs to be sent or no subtitle packet is recognized by the client SetTitlePacket titlePacket = new SetTitlePacket(); @@ -56,19 +64,20 @@ public class CooldownUtils { session.sendUpstreamPacket(titlePacket); session.setLastHitTime(System.currentTimeMillis()); long lastHitTime = session.getLastHitTime(); // Used later to prevent multiple scheduled cooldown threads - computeCooldown(session, lastHitTime); + computeCooldown(session, sessionPreference, lastHitTime); } /** * 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. */ - private static void computeCooldown(GeyserSession session, long lastHitTime) { + private static void computeCooldown(GeyserSession session, CooldownType sessionPreference, long lastHitTime) { if (session.isClosed()) return; // Don't run scheduled tasks if the client left if (lastHitTime != session.getLastHitTime()) return; // Means another cooldown has started so there's no need to continue this one SetTitlePacket titlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { titlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { titlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -79,10 +88,10 @@ public class CooldownUtils { titlePacket.setStayTime(2); session.sendUpstreamPacket(titlePacket); if (hasCooldown(session)) { - session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 + session.getConnector().getGeneralThreadPool().schedule(() -> computeCooldown(session, sessionPreference, lastHitTime), 50, TimeUnit.MILLISECONDS); // Updated per tick. 1000 divided by 20 ticks equals 50 } else { SetTitlePacket removeTitlePacket = new SetTitlePacket(); - if (SHOW_COOLDOWN == CooldownType.ACTIONBAR) { + if (sessionPreference == CooldownType.ACTIONBAR) { removeTitlePacket.setType(SetTitlePacket.Type.ACTIONBAR); } else { removeTitlePacket.setType(SetTitlePacket.Type.SUBTITLE); @@ -133,7 +142,7 @@ public class CooldownUtils { public static final CooldownType[] VALUES = values(); /** - * Convert the CooldownType string (from config) to the enum, TITLE on fail + * Convert the CooldownType string (from config) to the enum, DISABLED on fail * * @param name CooldownType string * diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index fa6e8c024..0e2a54a3c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -34,6 +34,8 @@ import org.geysermc.cumulus.CustomForm; import org.geysermc.cumulus.component.DropdownComponent; import org.geysermc.cumulus.response.CustomFormResponse; +import java.util.ArrayList; + public class SettingsUtils { /** * Build a settings form for the given session and store it for later @@ -49,12 +51,23 @@ public class SettingsUtils { .title("geyser.settings.title.main") .iconPath("textures/ui/settings_glyph_color_2x.png"); - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { - builder.label("geyser.settings.title.client") - .toggle("geyser.settings.option.coordinates", session.getWorldCache().isPrefersShowCoordinates()); - } + // Only show the client title if any of the client settings are available + if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + builder.label("geyser.settings.title.client"); + // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. + if (session.getPreferencesCache().isAllowShowCoordinates()) { + builder.toggle("geyser.settings.option.coordinates", session.getPreferencesCache().isPrefersShowCoordinates()); + } + + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + DropdownComponent.Builder cooldownDropdown = DropdownComponent.builder("options.attackIndicator"); + cooldownDropdown.option("options.attack.crosshair", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.TITLE); + cooldownDropdown.option("options.attack.hotbar", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.ACTIONBAR); + cooldownDropdown.option("options.off", session.getPreferencesCache().getCooldownPreference() == CooldownUtils.CooldownType.DISABLED); + builder.dropdown(cooldownDropdown); + } + } if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { builder.label("geyser.settings.title.server"); @@ -97,13 +110,22 @@ public class SettingsUtils { return; } - // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. - if (!session.isReducedDebugInfo() && session.getConnector().getConfig().isShowCoordinates()) { - response.skip(); // Client settings title + if (session.getPreferencesCache().isAllowShowCoordinates() || CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + response.skip(); // Client settings title - session.getWorldCache().setPrefersShowCoordinates(response.next()); - session.getWorldCache().updateShowCoordinates(); - } + // Client can only see its coordinates if reducedDebugInfo is disabled and coordinates are enabled in geyser config. + if (session.getPreferencesCache().isAllowShowCoordinates()) { + session.getPreferencesCache().setPrefersShowCoordinates(response.next()); + session.getPreferencesCache().updateShowCoordinates(); + response.skip(); + } + + if (CooldownUtils.getDefaultShowCooldown() != CooldownUtils.CooldownType.DISABLED) { + CooldownUtils.CooldownType cooldownType = CooldownUtils.CooldownType.VALUES[(int) response.next()]; + session.getPreferencesCache().setCooldownPreference(cooldownType); + response.skip(); + } + } if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { GameMode gameMode = GameMode.values()[(int) response.next()]; diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 96e7ed66c..e1e8fd6c2 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 96e7ed66ccdafea0cc991b8004566d448e8f6e6a +Subproject commit e1e8fd6c2b8abf366e60085c23a55a2c943806ae