From 806ec35a84798938cd442f6186dc5549efbc7a95 Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 9 Sep 2023 00:34:19 +0200 Subject: [PATCH] Feature: Allow client-side "game settings" menu gamemode/difficulty changes (#4062) Removes difficulty/Gamemode settings from the custom server settings form since these are now present in the client side settings. --- .../world/GeyserFabricWorldManager.java | 6 +++ .../manager/GeyserSpigotWorldManager.java | 6 +++ .../geyser/level/GeyserWorldManager.java | 6 +++ .../geysermc/geyser/level/WorldManager.java | 18 +++++++ .../geyser/network/LoggingPacketHandler.java | 5 ++ .../registry/PacketTranslatorRegistry.java | 2 + .../BedrockSetDefaultGameTypeTranslator.java | 53 +++++++++++++++++++ .../BedrockSetDifficultyTranslator.java | 48 +++++++++++++++++ .../BedrockSetPlayerGameTypeTranslator.java | 30 +++++++++-- .../geysermc/geyser/util/SettingsUtils.java | 31 ----------- 10 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDefaultGameTypeTranslator.java create mode 100644 core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java diff --git a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java index 9cd01f993..923db9b25 100644 --- a/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java +++ b/bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.fabric.world; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo; import me.lucko.fabric.api.permissions.v0.Permissions; import net.minecraft.core.BlockPos; @@ -153,6 +154,11 @@ public class GeyserFabricWorldManager extends GeyserWorldManager { return Permissions.check(player, permission); } + @Override + public GameMode getDefaultGameMode(GeyserSession session) { + return GameMode.byId(server.getDefaultGameType().getId()); + } + @Nonnull @Override public CompletableFuture getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) { diff --git a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 056747d6a..c8ccfffd7 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.platform.spigot.world.manager; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import org.bukkit.Bukkit; @@ -171,6 +172,11 @@ public class GeyserSpigotWorldManager extends WorldManager { return gameRule.getDefaultIntValue(); } + @Override + public GameMode getDefaultGameMode(GeyserSession session) { + return GameMode.byId(Bukkit.getDefaultGameMode().ordinal()); + } + @Override public boolean hasPermission(GeyserSession session, String permission) { return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission); diff --git a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java index 8d4b3f2e6..a8e2d00ae 100644 --- a/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.level; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -160,6 +161,11 @@ public class GeyserWorldManager extends WorldManager { return gameRule.getDefaultIntValue(); } + @Override + public GameMode getDefaultGameMode(GeyserSession session) { + return GameMode.SURVIVAL; + } + @Override public boolean hasPermission(GeyserSession session, String permission) { return false; diff --git a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java index 006caff55..a4de993d2 100644 --- a/core/src/main/java/org/geysermc/geyser/level/WorldManager.java +++ b/core/src/main/java/org/geysermc/geyser/level/WorldManager.java @@ -170,6 +170,24 @@ public abstract class WorldManager { session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT)); } + /** + * Get the default game mode of the server + * + * @param session the player requesting the default game mode + * @return the default game mode of the server, or Survival if unknown. + */ + public abstract GameMode getDefaultGameMode(GeyserSession session); + + /** + * Change the default game mode of the session's server + * + * @param session the player making the change + * @param gameMode the new default game mode + */ + public void setDefaultGameMode(GeyserSession session, GameMode gameMode) { + session.sendCommand("defaultgamemode " + gameMode.name().toLowerCase(Locale.ROOT)); + } + /** * Change the difficulty of the Java server * diff --git a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java index 59b34af16..61c8dbe3d 100644 --- a/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java @@ -255,6 +255,11 @@ public class LoggingPacketHandler implements BedrockPacketHandler { return defaultHandler(packet); } + @Override + public PacketSignal handle(RequestPermissionsPacket packet) { + return defaultHandler(packet); + } + @Override public PacketSignal handle(ResourcePackChunkRequestPacket packet) { return defaultHandler(packet); diff --git a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java index 61999d4f6..dbc8e2e26 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java @@ -29,6 +29,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundDe import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket; import io.netty.channel.EventLoop; +import org.cloudburstmc.protocol.bedrock.packet.RequestPermissionsPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.session.GeyserSession; @@ -46,6 +47,7 @@ public class PacketTranslatorRegistry extends AbstractMappedRegistry { + + /** + * Sets the default game mode for the server via the Bedrock client's "world" menu (given sufficient permissions). + */ + @Override + public void translate(GeyserSession session, SetDefaultGameTypePacket packet) { + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { + session.getGeyser().getWorldManager().setDefaultGameMode(session, GameMode.byId(packet.getGamemode())); + } + // Stop the client from updating their own Gamemode without telling the server + // Can occur when client Gamemode is set to "default", and default game mode is changed. + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); + playerGameTypePacket.setGamemode(EntityUtils.toBedrockGamemode(session.getGameMode()).ordinal()); + session.sendUpstreamPacket(playerGameTypePacket); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java new file mode 100644 index 000000000..a36aa77df --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetDifficultyTranslator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2023 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.bedrock.entity.player; + +import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; +import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.protocol.PacketTranslator; +import org.geysermc.geyser.translator.protocol.Translator; + +@Translator(packet = SetDifficultyPacket.class) +public class BedrockSetDifficultyTranslator extends PacketTranslator { + + /** + * Sets the Java server's difficulty via the Bedrock client's "world" menu (given sufficient permissions). + */ + @Override + public void translate(GeyserSession session, SetDifficultyPacket packet) { + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { + if (packet.getDifficulty() != session.getWorldCache().getDifficulty().ordinal()) { + session.getGeyser().getWorldManager().setDifficulty(session, Difficulty.from(packet.getDifficulty())); + } + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java index 70768af34..a1c2c2987 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java @@ -25,23 +25,43 @@ package org.geysermc.geyser.translator.protocol.bedrock.entity.player; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; +import org.geysermc.geyser.util.EntityUtils; /** * In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server. - * Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this. + * With operator status, the Gamemode change is sent to the Java server, if it is not present, the gamemode is not changed. */ @Translator(packet = SetPlayerGameTypePacket.class) public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator { + /** + * Sets client game mode for the server via the Bedrock client's "world" menu (given sufficient permissions). + */ @Override public void translate(GeyserSession session, SetPlayerGameTypePacket packet) { - // no - SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); - playerGameTypePacket.setGamemode(session.getGameMode().ordinal()); - session.sendUpstreamPacket(playerGameTypePacket); + // yes, if you are OP + if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) { + if (packet.getGamemode() != session.getGameMode().ordinal()) { + // Bedrock has more Gamemodes than Java, leading to cases 5 (for "default") and 6 (for "spectator") being sent + // https://github.com/CloudburstMC/Protocol/blob/3.0/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/data/GameType.java + GameMode gameMode = switch (packet.getGamemode()) { + case 1 -> GameMode.CREATIVE; + case 2 -> GameMode.ADVENTURE; + case 5 -> session.getGeyser().getWorldManager().getDefaultGameMode(session); + case 6 -> GameMode.SPECTATOR; + default -> GameMode.SURVIVAL; + }; + session.getGeyser().getWorldManager().setPlayerGameMode(session, gameMode); + } + } else { + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); + playerGameTypePacket.setGamemode(EntityUtils.toBedrockGamemode(session.getGameMode()).ordinal()); + session.sendUpstreamPacket(playerGameTypePacket); + } } } diff --git a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java index 5957fb9d9..8a8d684f6 100644 --- a/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java @@ -25,8 +25,6 @@ package org.geysermc.geyser.util; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import org.geysermc.cumulus.component.DropdownComponent; import org.geysermc.cumulus.form.CustomForm; import org.geysermc.geyser.GeyserImpl; @@ -77,23 +75,6 @@ public class SettingsUtils { } } - boolean canModifyServer = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server"); - if (canModifyServer) { - builder.label("geyser.settings.title.server"); - - DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal"); - for (GameMode gamemode : GameMode.values()) { - gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode); - } - builder.dropdown(gamemodeDropdown); - - DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty"); - for (Difficulty difficulty : Difficulty.values()) { - difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty); - } - builder.dropdown(difficultyDropdown); - } - boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules"); if (showGamerules) { builder.label("geyser.settings.title.game_rules") @@ -128,18 +109,6 @@ public class SettingsUtils { } } - if (canModifyServer) { - GameMode gameMode = GameMode.values()[(int) response.next()]; - if (gameMode != null && gameMode != session.getGameMode()) { - session.getGeyser().getWorldManager().setPlayerGameMode(session, gameMode); - } - - Difficulty difficulty = Difficulty.values()[(int) response.next()]; - if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) { - session.getGeyser().getWorldManager().setDifficulty(session, difficulty); - } - } - if (showGamerules) { for (GameRule gamerule : GameRule.VALUES) { if (Boolean.class.equals(gamerule.getType())) {