Skip to content

Commit 806ec35

Browse files
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.
1 parent 007edcb commit 806ec35

File tree

10 files changed

+169
-36
lines changed

10 files changed

+169
-36
lines changed

bootstrap/fabric/src/main/java/org/geysermc/geyser/platform/fabric/world/GeyserFabricWorldManager.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package org.geysermc.geyser.platform.fabric.world;
2727

28+
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
2829
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
2930
import me.lucko.fabric.api.permissions.v0.Permissions;
3031
import net.minecraft.core.BlockPos;
@@ -153,6 +154,11 @@ public boolean hasPermission(GeyserSession session, String permission) {
153154
return Permissions.check(player, permission);
154155
}
155156

157+
@Override
158+
public GameMode getDefaultGameMode(GeyserSession session) {
159+
return GameMode.byId(server.getDefaultGameType().getId());
160+
}
161+
156162
@Nonnull
157163
@Override
158164
public CompletableFuture<com.github.steveice10.opennbt.tag.builtin.CompoundTag> getPickItemNbt(GeyserSession session, int x, int y, int z, boolean addNbtData) {

bootstrap/spigot/src/main/java/org/geysermc/geyser/platform/spigot/world/manager/GeyserSpigotWorldManager.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package org.geysermc.geyser.platform.spigot.world.manager;
2727

28+
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
2829
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
2930
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
3031
import org.bukkit.Bukkit;
@@ -171,6 +172,11 @@ public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
171172
return gameRule.getDefaultIntValue();
172173
}
173174

175+
@Override
176+
public GameMode getDefaultGameMode(GeyserSession session) {
177+
return GameMode.byId(Bukkit.getDefaultGameMode().ordinal());
178+
}
179+
174180
@Override
175181
public boolean hasPermission(GeyserSession session, String permission) {
176182
return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission);

core/src/main/java/org/geysermc/geyser/level/GeyserWorldManager.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package org.geysermc.geyser.level;
2727

28+
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
2829
import com.github.steveice10.mc.protocol.data.game.level.block.BlockEntityInfo;
2930
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
3031
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
@@ -160,6 +161,11 @@ public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
160161
return gameRule.getDefaultIntValue();
161162
}
162163

164+
@Override
165+
public GameMode getDefaultGameMode(GeyserSession session) {
166+
return GameMode.SURVIVAL;
167+
}
168+
163169
@Override
164170
public boolean hasPermission(GeyserSession session, String permission) {
165171
return false;

core/src/main/java/org/geysermc/geyser/level/WorldManager.java

+18
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ public void setPlayerGameMode(GeyserSession session, GameMode gameMode) {
170170
session.sendCommand("gamemode " + gameMode.name().toLowerCase(Locale.ROOT));
171171
}
172172

173+
/**
174+
* Get the default game mode of the server
175+
*
176+
* @param session the player requesting the default game mode
177+
* @return the default game mode of the server, or Survival if unknown.
178+
*/
179+
public abstract GameMode getDefaultGameMode(GeyserSession session);
180+
181+
/**
182+
* Change the default game mode of the session's server
183+
*
184+
* @param session the player making the change
185+
* @param gameMode the new default game mode
186+
*/
187+
public void setDefaultGameMode(GeyserSession session, GameMode gameMode) {
188+
session.sendCommand("defaultgamemode " + gameMode.name().toLowerCase(Locale.ROOT));
189+
}
190+
173191
/**
174192
* Change the difficulty of the Java server
175193
*

core/src/main/java/org/geysermc/geyser/network/LoggingPacketHandler.java

+5
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ public PacketSignal handle(RequestChunkRadiusPacket packet) {
255255
return defaultHandler(packet);
256256
}
257257

258+
@Override
259+
public PacketSignal handle(RequestPermissionsPacket packet) {
260+
return defaultHandler(packet);
261+
}
262+
258263
@Override
259264
public PacketSignal handle(ResourcePackChunkRequestPacket packet) {
260265
return defaultHandler(packet);

core/src/main/java/org/geysermc/geyser/registry/PacketTranslatorRegistry.java

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.ClientboundTabListPacket;
3030
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.level.ClientboundLightUpdatePacket;
3131
import io.netty.channel.EventLoop;
32+
import org.cloudburstmc.protocol.bedrock.packet.RequestPermissionsPacket;
3233
import org.geysermc.geyser.GeyserImpl;
3334
import org.geysermc.geyser.registry.loader.RegistryLoaders;
3435
import org.geysermc.geyser.session.GeyserSession;
@@ -46,6 +47,7 @@ public class PacketTranslatorRegistry<T> extends AbstractMappedRegistry<Class<?
4647
IGNORED_PACKETS.add(ClientboundLightUpdatePacket.class); // Light is handled on Bedrock for us
4748
IGNORED_PACKETS.add(ClientboundTabListPacket.class); // Cant be implemented in Bedrock
4849
IGNORED_PACKETS.add(ClientboundDelimiterPacket.class); // Not implemented, spams logs
50+
IGNORED_PACKETS.add(RequestPermissionsPacket.class); // Bedrock client asks permission to switch default game mode, but we handle this ourselves
4951
}
5052

5153
protected PacketTranslatorRegistry() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
27+
28+
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
29+
import org.cloudburstmc.protocol.bedrock.packet.SetDefaultGameTypePacket;
30+
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
31+
import org.geysermc.geyser.session.GeyserSession;
32+
import org.geysermc.geyser.translator.protocol.PacketTranslator;
33+
import org.geysermc.geyser.translator.protocol.Translator;
34+
import org.geysermc.geyser.util.EntityUtils;
35+
36+
@Translator(packet = SetDefaultGameTypePacket.class)
37+
public class BedrockSetDefaultGameTypeTranslator extends PacketTranslator<SetDefaultGameTypePacket> {
38+
39+
/**
40+
* Sets the default game mode for the server via the Bedrock client's "world" menu (given sufficient permissions).
41+
*/
42+
@Override
43+
public void translate(GeyserSession session, SetDefaultGameTypePacket packet) {
44+
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
45+
session.getGeyser().getWorldManager().setDefaultGameMode(session, GameMode.byId(packet.getGamemode()));
46+
}
47+
// Stop the client from updating their own Gamemode without telling the server
48+
// Can occur when client Gamemode is set to "default", and default game mode is changed.
49+
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
50+
playerGameTypePacket.setGamemode(EntityUtils.toBedrockGamemode(session.getGameMode()).ordinal());
51+
session.sendUpstreamPacket(playerGameTypePacket);
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
27+
28+
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
29+
import org.cloudburstmc.protocol.bedrock.packet.SetDifficultyPacket;
30+
import org.geysermc.geyser.session.GeyserSession;
31+
import org.geysermc.geyser.translator.protocol.PacketTranslator;
32+
import org.geysermc.geyser.translator.protocol.Translator;
33+
34+
@Translator(packet = SetDifficultyPacket.class)
35+
public class BedrockSetDifficultyTranslator extends PacketTranslator<SetDifficultyPacket> {
36+
37+
/**
38+
* Sets the Java server's difficulty via the Bedrock client's "world" menu (given sufficient permissions).
39+
*/
40+
@Override
41+
public void translate(GeyserSession session, SetDifficultyPacket packet) {
42+
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
43+
if (packet.getDifficulty() != session.getWorldCache().getDifficulty().ordinal()) {
44+
session.getGeyser().getWorldManager().setDifficulty(session, Difficulty.from(packet.getDifficulty()));
45+
}
46+
}
47+
}
48+
}

core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/entity/player/BedrockSetPlayerGameTypeTranslator.java

+25-5
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,43 @@
2525

2626
package org.geysermc.geyser.translator.protocol.bedrock.entity.player;
2727

28+
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
2829
import org.cloudburstmc.protocol.bedrock.packet.SetPlayerGameTypePacket;
2930
import org.geysermc.geyser.session.GeyserSession;
3031
import org.geysermc.geyser.translator.protocol.PacketTranslator;
3132
import org.geysermc.geyser.translator.protocol.Translator;
33+
import org.geysermc.geyser.util.EntityUtils;
3234

3335
/**
3436
* In vanilla Bedrock, if you have operator status, this sets the player's gamemode without confirmation from the server.
35-
* Since we have a custom server option to request the gamemode, we just reset the gamemode and ignore this.
37+
* With operator status, the Gamemode change is sent to the Java server, if it is not present, the gamemode is not changed.
3638
*/
3739
@Translator(packet = SetPlayerGameTypePacket.class)
3840
public class BedrockSetPlayerGameTypeTranslator extends PacketTranslator<SetPlayerGameTypePacket> {
3941

42+
/**
43+
* Sets client game mode for the server via the Bedrock client's "world" menu (given sufficient permissions).
44+
*/
4045
@Override
4146
public void translate(GeyserSession session, SetPlayerGameTypePacket packet) {
42-
// no
43-
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
44-
playerGameTypePacket.setGamemode(session.getGameMode().ordinal());
45-
session.sendUpstreamPacket(playerGameTypePacket);
47+
// yes, if you are OP
48+
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
49+
if (packet.getGamemode() != session.getGameMode().ordinal()) {
50+
// Bedrock has more Gamemodes than Java, leading to cases 5 (for "default") and 6 (for "spectator") being sent
51+
// https://github.com/CloudburstMC/Protocol/blob/3.0/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/data/GameType.java
52+
GameMode gameMode = switch (packet.getGamemode()) {
53+
case 1 -> GameMode.CREATIVE;
54+
case 2 -> GameMode.ADVENTURE;
55+
case 5 -> session.getGeyser().getWorldManager().getDefaultGameMode(session);
56+
case 6 -> GameMode.SPECTATOR;
57+
default -> GameMode.SURVIVAL;
58+
};
59+
session.getGeyser().getWorldManager().setPlayerGameMode(session, gameMode);
60+
}
61+
} else {
62+
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
63+
playerGameTypePacket.setGamemode(EntityUtils.toBedrockGamemode(session.getGameMode()).ordinal());
64+
session.sendUpstreamPacket(playerGameTypePacket);
65+
}
4666
}
4767
}

core/src/main/java/org/geysermc/geyser/util/SettingsUtils.java

-31
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525

2626
package org.geysermc.geyser.util;
2727

28-
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
29-
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
3028
import org.geysermc.cumulus.component.DropdownComponent;
3129
import org.geysermc.cumulus.form.CustomForm;
3230
import org.geysermc.geyser.GeyserImpl;
@@ -77,23 +75,6 @@ public static CustomForm buildForm(GeyserSession session) {
7775
}
7876
}
7977

80-
boolean canModifyServer = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server");
81-
if (canModifyServer) {
82-
builder.label("geyser.settings.title.server");
83-
84-
DropdownComponent.Builder gamemodeDropdown = DropdownComponent.builder("%createWorldScreen.gameMode.personal");
85-
for (GameMode gamemode : GameMode.values()) {
86-
gamemodeDropdown.option("selectWorld.gameMode." + gamemode.name().toLowerCase(), session.getGameMode() == gamemode);
87-
}
88-
builder.dropdown(gamemodeDropdown);
89-
90-
DropdownComponent.Builder difficultyDropdown = DropdownComponent.builder("%options.difficulty");
91-
for (Difficulty difficulty : Difficulty.values()) {
92-
difficultyDropdown.option("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty);
93-
}
94-
builder.dropdown(difficultyDropdown);
95-
}
96-
9778
boolean showGamerules = session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules");
9879
if (showGamerules) {
9980
builder.label("geyser.settings.title.game_rules")
@@ -128,18 +109,6 @@ public static CustomForm buildForm(GeyserSession session) {
128109
}
129110
}
130111

131-
if (canModifyServer) {
132-
GameMode gameMode = GameMode.values()[(int) response.next()];
133-
if (gameMode != null && gameMode != session.getGameMode()) {
134-
session.getGeyser().getWorldManager().setPlayerGameMode(session, gameMode);
135-
}
136-
137-
Difficulty difficulty = Difficulty.values()[(int) response.next()];
138-
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
139-
session.getGeyser().getWorldManager().setDifficulty(session, difficulty);
140-
}
141-
}
142-
143112
if (showGamerules) {
144113
for (GameRule gamerule : GameRule.VALUES) {
145114
if (Boolean.class.equals(gamerule.getType())) {

0 commit comments

Comments
 (0)