Add support for client side settings (#1035)

* Port code from #486

Co-authored-by: Luke <32024335+lukeeey@users.noreply.github.com>

* Fix and clean code and add default gamemode changing

* Clean copyright

* Remove direct modification of server, clean up code and add player list xuid fetching.

* Move to custom settings menu

* Move sendAdventureSettings to GeyserSession

* Add javadoc comments

* Add translation support

* Remove updated copyright

* Clean up

* Clarify some javadoc comments

* Remove obsolete code

* Update languages submodule

* Fix javadoc comments

* Fix compile

Co-authored-by: Luke <32024335+lukeeey@users.noreply.github.com>
Co-authored-by: Redned <redned235@gmail.com>
This commit is contained in:
rtm516 2020-08-08 23:41:12 +01:00 committed by GitHub
parent 0fde30fc78
commit 0a5048232f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 668 additions and 83 deletions

View file

@ -25,17 +25,19 @@
package org.geysermc.platform.spigot.world;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import lombok.AllArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.block.Block;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.world.WorldManager;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.GameRule;
import us.myles.ViaVersion.protocols.protocol1_13_1to1_13.Protocol1_13_1To1_13;
import us.myles.ViaVersion.protocols.protocol1_16to1_15_2.data.MappingData;
@AllArgsConstructor
public class GeyserSpigotWorldManager extends WorldManager {
public class GeyserSpigotWorldManager extends GeyserWorldManager {
private final boolean isLegacy;
// You need ViaVersion to connect to an older server with Geyser.
@ -69,4 +71,19 @@ public class GeyserSpigotWorldManager extends WorldManager {
return BlockTranslator.AIR;
}
}
@Override
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
}
@Override
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
return Integer.parseInt(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID()));
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
return Bukkit.getPlayer(session.getPlayerEntity().getUsername()).hasPermission(permission);
}
}

View file

@ -96,6 +96,12 @@
<version>8.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-object-object-maps</artifactId>
<version>8.3.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View file

@ -30,14 +30,14 @@ import org.geysermc.connector.ping.IGeyserPingPassthrough;
import org.geysermc.connector.configuration.GeyserConfiguration;
import org.geysermc.connector.GeyserLogger;
import org.geysermc.connector.command.CommandManager;
import org.geysermc.connector.network.translators.world.CachedChunkManager;
import org.geysermc.connector.network.translators.world.GeyserWorldManager;
import org.geysermc.connector.network.translators.world.WorldManager;
import java.nio.file.Path;
public interface GeyserBootstrap {
CachedChunkManager DEFAULT_CHUNK_MANAGER = new CachedChunkManager();
GeyserWorldManager DEFAULT_CHUNK_MANAGER = new GeyserWorldManager();
/**
* Called when the GeyserBootstrap is enabled

View file

@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat
import com.github.steveice10.mc.protocol.data.message.TextMessage;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.nukkitx.math.vector.Vector3f;
import com.nukkitx.protocol.bedrock.data.AdventureSetting;
import com.nukkitx.protocol.bedrock.data.AttributeData;
import com.nukkitx.protocol.bedrock.data.PlayerPermission;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
@ -47,10 +48,7 @@ import org.geysermc.connector.scoreboard.Team;
import org.geysermc.connector.utils.AttributeUtils;
import org.geysermc.connector.utils.MessageUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Getter @Setter
@ -95,7 +93,7 @@ public class PlayerEntity extends LivingEntity {
addPlayerPacket.setMotion(motion);
addPlayerPacket.setHand(hand);
addPlayerPacket.getAdventureSettings().setCommandPermission(CommandPermission.NORMAL);
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.VISITOR);
addPlayerPacket.getAdventureSettings().setPlayerPermission(PlayerPermission.MEMBER);
addPlayerPacket.setDeviceId("");
addPlayerPacket.setPlatformChatId("");
addPlayerPacket.getMetadata().putAll(metadata);
@ -212,7 +210,7 @@ public class PlayerEntity extends LivingEntity {
if (entityMetadata.getId() == 2) {
// System.out.println(session.getScoreboardCache().getScoreboard().getObjectives().keySet());
for (Team team : session.getScoreboardCache().getScoreboard().getTeams().values()) {
for (Team team : session.getWorldCache().getScoreboard().getTeams().values()) {
// session.getConnector().getLogger().info("team name " + team.getName());
// session.getConnector().getLogger().info("team entities " + team.getEntities());
}
@ -221,7 +219,7 @@ public class PlayerEntity extends LivingEntity {
if (name != null) {
username = MessageUtils.getBedrockMessage(name);
}
Team team = session.getScoreboardCache().getScoreboard().getTeamFor(username);
Team team = session.getWorldCache().getScoreboard().getTeamFor(username);
if (team != null) {
// session.getConnector().getLogger().info("team name es " + team.getName() + " with prefix " + team.getPrefix() + " and suffix " + team.getSuffix());
metadata.put(EntityData.NAMETAG, team.getPrefix() + MessageUtils.toChatColor(team.getColor()) + username + team.getSuffix());

View file

@ -34,6 +34,7 @@ import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslatorRegistry;
import org.geysermc.connector.utils.LoginEncryptionUtils;
import org.geysermc.connector.utils.LanguageUtils;
import org.geysermc.connector.utils.SettingsUtils;
public class UpstreamPacketHandler extends LoggingPacketHandler {
@ -91,6 +92,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {
@Override
public boolean handle(ModalFormResponsePacket packet) {
if (packet.getFormId() == SettingsUtils.SETTINGS_FORM_ID) {
return SettingsUtils.handleSettingsForm(session, packet.getFormData());
}
return LoginEncryptionUtils.authenticateFromForm(session, connector, packet.getFormId(), packet.getFormData());
}

View file

@ -46,6 +46,7 @@ import com.nukkitx.math.vector.*;
import com.nukkitx.protocol.bedrock.BedrockPacket;
import com.nukkitx.protocol.bedrock.BedrockServerSession;
import com.nukkitx.protocol.bedrock.data.*;
import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
@ -54,6 +55,7 @@ import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.FormWindow;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.command.CommandSender;
@ -79,9 +81,7 @@ import java.net.InetSocketAddress;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@Getter
@ -102,7 +102,7 @@ public class GeyserSession implements CommandSender {
private ChunkCache chunkCache;
private EntityCache entityCache;
private InventoryCache inventoryCache;
private ScoreboardCache scoreboardCache;
private WorldCache worldCache;
private WindowCache windowCache;
@Setter
private TeleportCache teleportCache;
@ -191,6 +191,41 @@ public class GeyserSession implements CommandSender {
private MinecraftProtocol protocol;
private boolean reducedDebugInfo = false;
@Setter
private CustomFormWindow settingsForm;
/**
* The op permission level set by the server
*/
@Setter
private int opPermissionLevel = 0;
/**
* If the current player can fly
*/
@Setter
private boolean canFly = false;
/**
* If the current player is flying
*/
@Setter
private boolean flying = false;
/**
* If the current player is in noclip
*/
@Setter
private boolean noClip = false;
/**
* If the current player can not interact with the world
*/
@Setter
private boolean worldImmutable = false;
public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) {
this.connector = connector;
this.upstream = new UpstreamSession(bedrockServerSession);
@ -198,7 +233,7 @@ public class GeyserSession implements CommandSender {
this.chunkCache = new ChunkCache(this);
this.entityCache = new EntityCache(this);
this.inventoryCache = new InventoryCache(this);
this.scoreboardCache = new ScoreboardCache(this);
this.worldCache = new WorldCache(this);
this.windowCache = new WindowCache(this);
this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO);
@ -440,7 +475,7 @@ public class GeyserSession implements CommandSender {
this.chunkCache = null;
this.entityCache = null;
this.scoreboardCache = null;
this.worldCache = null;
this.inventoryCache = null;
this.windowCache = null;
@ -605,4 +640,66 @@ public class GeyserSession implements CommandSender {
connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server");
}
}
/**
* Update the cached value for the reduced debug info gamerule.
* This also toggles the coordinates display
*
* @param value The new value for reducedDebugInfo
*/
public void setReducedDebugInfo(boolean value) {
worldCache.setShowCoordinates(!value);
reducedDebugInfo = value;
}
/**
* Send a gamerule value to the client
*
* @param gameRule The gamerule to send
* @param value The value of the gamerule
*/
public void sendGameRule(String gameRule, Object value) {
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>(gameRule, value));
upstream.sendPacket(gameRulesChangedPacket);
}
/**
* @see org.geysermc.connector.network.translators.world.WorldManager#hasPermission(GeyserSession, String)
*/
public Boolean hasPermission(String permission) {
return connector.getWorldManager().hasPermission(this, permission);
}
/**
* Send an AdventureSettingsPacket to the client with the latest flags
*/
public void sendAdventureSettings() {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setUniqueEntityId(playerEntity.getGeyserId());
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
Set<AdventureSetting> flags = new HashSet<>();
if (canFly) {
flags.add(AdventureSetting.MAY_FLY);
}
if (flying) {
flags.add(AdventureSetting.FLYING);
}
if (worldImmutable) {
flags.add(AdventureSetting.WORLD_IMMUTABLE);
}
if (noClip) {
flags.add(AdventureSetting.NO_CLIP);
}
flags.add(AdventureSetting.AUTO_JUMP);
adventureSettingsPacket.getSettings().addAll(flags);
sendUpstreamPacket(adventureSettingsPacket);
}
}

View file

@ -25,7 +25,9 @@
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.network.session.GeyserSession;
import org.geysermc.connector.scoreboard.Objective;
import org.geysermc.connector.scoreboard.Scoreboard;
@ -33,11 +35,18 @@ import org.geysermc.connector.scoreboard.Scoreboard;
import java.util.Collection;
@Getter
public class ScoreboardCache {
public class WorldCache {
private GeyserSession session;
@Setter
private Difficulty difficulty = Difficulty.EASY;
private boolean showCoordinates = true;
private Scoreboard scoreboard;
public ScoreboardCache(GeyserSession session) {
public WorldCache(GeyserSession session) {
this.session = session;
this.scoreboard = new Scoreboard(session);
}
@ -52,4 +61,14 @@ public class ScoreboardCache {
}
}
}
/**
* Tell the client to hide or show the coordinates
*
* @param value True to show, false to hide
*/
public void setShowCoordinates(boolean value) {
showCoordinates = value;
session.sendGameRule("showcoordinates", value);
}
}

View file

@ -23,15 +23,25 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.world;
package org.geysermc.connector.network.translators.bedrock;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.nukkitx.protocol.bedrock.packet.ServerSettingsRequestPacket;
import com.nukkitx.protocol.bedrock.packet.ServerSettingsResponsePacket;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.utils.SettingsUtils;
public class CachedChunkManager extends WorldManager {
@Translator(packet = ServerSettingsRequestPacket.class)
public class BedrockServerSettingsRequestTranslator extends PacketTranslator<ServerSettingsRequestPacket> {
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
public void translate(ServerSettingsRequestPacket packet, GeyserSession session) {
SettingsUtils.buildForm(session);
ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket();
serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData());
serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID);
session.sendUpstreamPacket(serverSettingsResponsePacket);
}
}

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity;
import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade;
import com.github.steveice10.mc.protocol.data.game.window.WindowType;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.packet.EmotePacket;
import org.geysermc.connector.GeyserConnector;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.protocol.bedrock.data.entity.EntityData;
import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.entity.player;
import com.nukkitx.math.vector.Vector3d;
import org.geysermc.connector.common.ChatColor;

View file

@ -23,7 +23,7 @@
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.connector.network.translators.bedrock;
package org.geysermc.connector.network.translators.bedrock.world;
import com.nukkitx.protocol.bedrock.data.SoundEvent;
import com.nukkitx.protocol.bedrock.packet.LevelSoundEventPacket;

View file

@ -40,5 +40,7 @@ public class JavaDifficultyTranslator extends PacketTranslator<ServerDifficultyP
SetDifficultyPacket setDifficultyPacket = new SetDifficultyPacket();
setDifficultyPacket.setDifficulty(packet.getDifficulty().ordinal());
session.sendUpstreamPacket(setDifficultyPacket);
session.getWorldCache().setDifficulty(packet.getDifficulty());
}
}

View file

@ -56,7 +56,7 @@ public class JavaJoinGameTranslator extends PacketTranslator<ServerJoinGamePacke
DimensionUtils.switchDimension(session, fakeDim);
DimensionUtils.switchDimension(session, packet.getDimension());
session.getScoreboardCache().removeScoreboard();
session.getWorldCache().removeScoreboard();
}
AdventureSettingsPacket bedrockPacket = new AdventureSettingsPacket();

View file

@ -31,6 +31,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.EntityEventPacket;
import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.entity.type.EntityType;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
@ -51,6 +52,33 @@ public class JavaEntityStatusTranslator extends PacketTranslator<ServerEntitySta
EntityEventPacket entityEventPacket = new EntityEventPacket();
entityEventPacket.setRuntimeEntityId(entity.getGeyserId());
switch (packet.getStatus()) {
case PLAYER_ENABLE_REDUCED_DEBUG:
session.setReducedDebugInfo(true);
return;
case PLAYER_DISABLE_REDUCED_DEBUG:
session.setReducedDebugInfo(false);
return;
case PLAYER_OP_PERMISSION_LEVEL_0:
session.setOpPermissionLevel(0);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_1:
session.setOpPermissionLevel(1);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_2:
session.setOpPermissionLevel(2);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_3:
session.setOpPermissionLevel(3);
session.sendAdventureSettings();
return;
case PLAYER_OP_PERMISSION_LEVEL_4:
session.setOpPermissionLevel(4);
session.sendAdventureSettings();
return;
// EntityEventType.HURT sends extra data depending on the type of damage. However this appears to have no visual changes
case LIVING_BURN:
case LIVING_DROWN:

View file

@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission;
import com.nukkitx.protocol.bedrock.packet.AdventureSettingsPacket;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -43,24 +44,12 @@ public class JavaPlayerAbilitiesTranslator extends PacketTranslator<ServerPlayer
@Override
public void translate(ServerPlayerAbilitiesPacket packet, GeyserSession session) {
Entity entity = session.getPlayerEntity();
PlayerEntity entity = session.getPlayerEntity();
if (entity == null)
return;
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
playerFlags.add(AdventureSetting.AUTO_JUMP);
if (packet.isCanFly())
playerFlags.add(AdventureSetting.MAY_FLY);
if (packet.isFlying())
playerFlags.add(AdventureSetting.FLYING);
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
// Required or the packet simply is not sent
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
adventureSettingsPacket.getSettings().addAll(playerFlags);
session.sendUpstreamPacket(adventureSettingsPacket);
session.setCanFly(packet.isCanFly());
session.setFlying(packet.isFlying());
session.sendAdventureSettings();
}
}

View file

@ -36,7 +36,7 @@ public class JavaDisplayScoreboardTranslator extends PacketTranslator<ServerDisp
@Override
public void translate(ServerDisplayScoreboardPacket packet, GeyserSession session) {
session.getScoreboardCache().getScoreboard().registerNewObjective(
session.getWorldCache().getScoreboard().registerNewObjective(
packet.getName(), packet.getPosition()
);
}

View file

@ -26,7 +26,7 @@
package org.geysermc.connector.network.translators.java.scoreboard;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.cache.ScoreboardCache;
import org.geysermc.connector.network.session.cache.WorldCache;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
import org.geysermc.connector.scoreboard.Objective;
@ -41,7 +41,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator<ServerSc
@Override
public void translate(ServerScoreboardObjectivePacket packet, GeyserSession session) {
ScoreboardCache cache = session.getScoreboardCache();
WorldCache cache = session.getWorldCache();
Scoreboard scoreboard = cache.getScoreboard();
Objective objective = scoreboard.getObjective(packet.getName());

View file

@ -47,7 +47,7 @@ public class JavaTeamTranslator extends PacketTranslator<ServerTeamPacket> {
public void translate(ServerTeamPacket packet, GeyserSession session) {
GeyserConnector.getInstance().getLogger().debug("Team packet " + packet.getTeamName() + " " + packet.getAction() + " " + Arrays.toString(packet.getPlayers()));
Scoreboard scoreboard = session.getScoreboardCache().getScoreboard();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Team team = scoreboard.getTeam(packet.getTeamName());
switch (packet.getAction()) {
case CREATE:

View file

@ -42,7 +42,7 @@ public class JavaUpdateScoreTranslator extends PacketTranslator<ServerUpdateScor
@Override
public void translate(ServerUpdateScorePacket packet, GeyserSession session) {
try {
Scoreboard scoreboard = session.getScoreboardCache().getScoreboard();
Scoreboard scoreboard = session.getWorldCache().getScoreboard();
Objective objective = scoreboard.getObjective(packet.getObjective());
if (objective == null && packet.getAction() != ScoreboardAction.REMOVE) {

View file

@ -41,6 +41,7 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityEventType;
import com.nukkitx.protocol.bedrock.packet.*;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.translators.PacketTranslator;
import org.geysermc.connector.network.translators.Translator;
@ -55,7 +56,7 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
@Override
public void translate(ServerNotifyClientPacket packet, GeyserSession session) {
Entity entity = session.getPlayerEntity();
PlayerEntity entity = session.getPlayerEntity();
if (entity == null)
return;
@ -75,39 +76,17 @@ public class JavaNotifyClientTranslator extends PacketTranslator<ServerNotifyCli
session.sendUpstreamPacket(stopRainPacket);
break;
case CHANGE_GAMEMODE:
Set<AdventureSetting> playerFlags = new ObjectOpenHashSet<>();
GameMode gameMode = (GameMode) packet.getValue();
if (gameMode == GameMode.ADVENTURE)
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
if (gameMode == GameMode.CREATIVE)
playerFlags.add(AdventureSetting.MAY_FLY);
if (gameMode == GameMode.SPECTATOR) {
playerFlags.add(AdventureSetting.MAY_FLY);
playerFlags.add(AdventureSetting.NO_CLIP);
playerFlags.add(AdventureSetting.FLYING);
playerFlags.add(AdventureSetting.WORLD_IMMUTABLE);
gameMode = GameMode.CREATIVE; // spectator doesnt exist on bedrock
}
playerFlags.add(AdventureSetting.AUTO_JUMP);
session.setNoClip(gameMode == GameMode.SPECTATOR);
session.setWorldImmutable(gameMode == GameMode.ADVENTURE || gameMode == GameMode.SPECTATOR);
session.sendAdventureSettings();
SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket();
playerGameTypePacket.setGamemode(gameMode.ordinal());
session.sendUpstreamPacket(playerGameTypePacket);
session.setGameMode(gameMode);
// We need to delay this because otherwise it's overridden by the adventure settings from the abilities packet
session.getConnector().getGeneralThreadPool().schedule(() -> {
AdventureSettingsPacket adventureSettingsPacket = new AdventureSettingsPacket();
adventureSettingsPacket.setPlayerPermission(PlayerPermission.MEMBER);
adventureSettingsPacket.setCommandPermission(CommandPermission.NORMAL);
adventureSettingsPacket.setUniqueEntityId(entity.getGeyserId());
adventureSettingsPacket.getSettings().addAll(playerFlags);
session.sendUpstreamPacket(adventureSettingsPacket);
}, 50, TimeUnit.MILLISECONDS);
// Update the crafting grid to add/remove barriers for creative inventory
PlayerInventoryTranslator.updateCraftingGrid(session, session.getInventory());
break;

View file

@ -67,9 +67,7 @@ public class JavaUpdateTimeTranslator extends PacketTranslator<ServerUpdateTimeP
}
private void setDoDaylightCycleGamerule(GeyserSession session, boolean doCycle) {
GameRulesChangedPacket gameRulesChangedPacket = new GameRulesChangedPacket();
gameRulesChangedPacket.getGameRules().add(new GameRuleData<>("dodaylightcycle", doCycle));
session.sendUpstreamPacket(gameRulesChangedPacket);
session.sendGameRule("dodaylightcycle", doCycle);
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2019-2020 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.translators.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.GameRule;
public class GeyserWorldManager extends WorldManager {
private static final Object2ObjectMap<String, String> gameruleCache = new Object2ObjectOpenHashMap<>();
@Override
public int getBlockAt(GeyserSession session, int x, int y, int z) {
return session.getChunkCache().getBlockAt(new Position(x, y, z));
}
@Override
public void setGameRule(GeyserSession session, String name, Object value) {
session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value));
gameruleCache.put(name, String.valueOf(value));
}
@Override
public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) {
String value = gameruleCache.get(gameRule.getJavaID());
if (value != null) {
return Boolean.parseBoolean(value);
}
return gameRule.getDefaultValue() != null ? (Boolean) gameRule.getDefaultValue() : false;
}
@Override
public int getGameRuleInt(GeyserSession session, GameRule gameRule) {
String value = gameruleCache.get(gameRule.getJavaID());
if (value != null) {
return Integer.parseInt(value);
}
return gameRule.getDefaultValue() != null ? (int) gameRule.getDefaultValue() : 0;
}
@Override
public void setPlayerGameMode(GeyserSession session, GameMode gameMode) {
session.sendDownstreamPacket(new ClientChatPacket("/gamemode " + gameMode.name().toLowerCase()));
}
@Override
public void setDifficulty(GeyserSession session, Difficulty difficulty) {
session.sendDownstreamPacket(new ClientChatPacket("/difficulty " + difficulty.name().toLowerCase()));
}
@Override
public boolean hasPermission(GeyserSession session, String permission) {
return false;
}
}

View file

@ -26,8 +26,11 @@
package org.geysermc.connector.network.translators.world;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import com.nukkitx.math.vector.Vector3i;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.utils.GameRule;
/**
* Class that manages or retrieves various information
@ -70,4 +73,56 @@ public abstract class WorldManager {
* @return the block state at the specified location
*/
public abstract int getBlockAt(GeyserSession session, int x, int y, int z);
/**
* Updates a gamerule value on the Java server
*
* @param session The session of the user that requested the change
* @param name The gamerule to change
* @param value The new value for the gamerule
*/
public abstract void setGameRule(GeyserSession session, String name, Object value);
/**
* Get a gamerule value as a boolean
*
* @param session The session of the user that requested the value
* @param gameRule The gamerule to fetch the value of
* @return The boolean representation of the value
*/
public abstract Boolean getGameRuleBool(GeyserSession session, GameRule gameRule);
/**
* Get a gamerule value as an integer
*
* @param session The session of the user that requested the value
* @param gameRule The gamerule to fetch the value of
* @return The integer representation of the value
*/
public abstract int getGameRuleInt(GeyserSession session, GameRule gameRule);
/**
* Change the game mode of the given session
*
* @param session The session of the player to change the game mode of
* @param gameMode The game mode to change the player to
*/
public abstract void setPlayerGameMode(GeyserSession session, GameMode gameMode);
/**
* Change the difficulty of the Java server
*
* @param session The session of the user that requested the change
* @param difficulty The difficulty to change to
*/
public abstract void setDifficulty(GeyserSession session, Difficulty difficulty);
/**
* Checks if the given session's player has a permission
*
* @param session The session of the player to check the permission of
* @param permission The permission node to check
* @return True if the player has the requested permission, false if not
*/
public abstract boolean hasPermission(GeyserSession session, String permission);
}

View file

@ -0,0 +1,123 @@
/*
* Copyright (c) 2019-2020 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.utils;
import lombok.Getter;
/**
* This enum stores each gamerule along with the value type and the default.
* It is used to construct the list for the settings menu
*/
public enum GameRule {
ANNOUNCEADVANCEMENTS("announceAdvancements", Boolean.class, true), // JE only
COMMANDBLOCKOUTPUT("commandBlockOutput", Boolean.class, true),
DISABLEELYTRAMOVEMENTCHECK("disableElytraMovementCheck", Boolean.class, false), // JE only
DISABLERAIDS("disableRaids", Boolean.class, false), // JE only
DODAYLIGHTCYCLE("doDaylightCycle", Boolean.class, true),
DOENTITYDROPS("doEntityDrops", Boolean.class, true),
DOFIRETICK("doFireTick", Boolean.class, true),
DOIMMEDIATERESPAWN("doImmediateRespawn", Boolean.class, false),
DOINSOMNIA("doInsomnia", Boolean.class, true),
DOLIMITEDCRAFTING("doLimitedCrafting", Boolean.class, false), // JE only
DOMOBLOOT("doMobLoot", Boolean.class, true),
DOMOBSPAWNING("doMobSpawning", Boolean.class, true),
DOPATROLSPAWNING("doPatrolSpawning", Boolean.class, true), // JE only
DOTILEDROPS("doTileDrops", Boolean.class, true),
DOTRADERSPAWNING("doTraderSpawning", Boolean.class, true), // JE only
DOWEATHERCYCLE("doWeatherCycle", Boolean.class, true),
DROWNINGDAMAGE("drowningDamage", Boolean.class, true),
FALLDAMAGE("fallDamage", Boolean.class, true),
FIREDAMAGE("fireDamage", Boolean.class, true),
FORGIVEDEADPLAYERS("forgiveDeadPlayers", Boolean.class, true), // JE only
KEEPINVENTORY("keepInventory", Boolean.class, false),
LOGADMINCOMMANDS("logAdminCommands", Boolean.class, true), // JE only
MAXCOMMANDCHAINLENGTH("maxCommandChainLength", Integer.class, 65536),
MAXENTITYCRAMMING("maxEntityCramming", Integer.class, 24), // JE only
MOBGRIEFING("mobGriefing", Boolean.class, true),
NATURALREGENERATION("naturalRegeneration", Boolean.class, true),
RANDOMTICKSPEED("randomTickSpeed", Integer.class, 3),
REDUCEDDEBUGINFO("reducedDebugInfo", Boolean.class, false), // JE only
SENDCOMMANDFEEDBACK("sendCommandFeedback", Boolean.class, true),
SHOWDEATHMESSAGES("showDeathMessages", Boolean.class, true),
SPAWNRADIUS("spawnRadius", Integer.class, 10),
SPECTATORSGENERATECHUNKS("spectatorsGenerateChunks", Boolean.class, true), // JE only
UNIVERSALANGER("universalAnger", Boolean.class, false), // JE only
UNKNOWN("unknown", Object.class);
private static final GameRule[] VALUES = values();
@Getter
private String javaID;
@Getter
private Class<?> type;
@Getter
private Object defaultValue;
GameRule(String javaID, Class<?> type) {
this(javaID, type, null);
}
GameRule(String javaID, Class<?> type, Object defaultValue) {
this.javaID = javaID;
this.type = type;
this.defaultValue = defaultValue;
}
/**
* Convert a string to an object of the correct type for the current gamerule
*
* @param value The string value to convert
* @return The converted and formatted value
*/
public Object convertValue(String value) {
if (type.equals(Boolean.class)) {
return Boolean.parseBoolean(value);
} else if (type.equals(Integer.class)) {
return Integer.parseInt(value);
}
return null;
}
/**
* Fetch a game rule by the given Java ID
*
* @param id The ID of the gamerule
* @return A {@link GameRule} object representing the requested ID or {@link GameRule.UNKNOWN}
*/
public static GameRule fromJavaID(String id) {
for (GameRule gamerule : VALUES) {
if (gamerule.javaID.equals(id)) {
return gamerule;
}
}
return UNKNOWN;
}
}

View file

@ -0,0 +1,163 @@
/*
* Copyright (c) 2019-2020 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.utils;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.data.game.setting.Difficulty;
import org.geysermc.common.window.CustomFormBuilder;
import org.geysermc.common.window.CustomFormWindow;
import org.geysermc.common.window.button.FormImage;
import org.geysermc.common.window.component.DropdownComponent;
import org.geysermc.common.window.component.InputComponent;
import org.geysermc.common.window.component.LabelComponent;
import org.geysermc.common.window.component.ToggleComponent;
import org.geysermc.common.window.response.CustomFormResponse;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.network.session.GeyserSession;
import java.util.ArrayList;
public class SettingsUtils {
// Used in UpstreamPacketHandler.java
public static final int SETTINGS_FORM_ID = 1338;
/**
* Build a settings form for the given session and store it for later
*
* @param session The session to build the form for
*/
public static void buildForm(GeyserSession session) {
// Cache the language for cleaner access
String language = session.getClientData().getLanguageCode();
CustomFormBuilder builder = new CustomFormBuilder(LanguageUtils.getPlayerLocaleString("geyser.settings.title.main", language));
builder.setIcon(new FormImage(FormImage.FormImageType.PATH, "textures/ui/settings_glyph_color_2x.png"));
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.client", language)));
builder.addComponent(new ToggleComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.option.coordinates", language, session.getWorldCache().isShowCoordinates())));
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.server", language)));
DropdownComponent gamemodeDropdown = new DropdownComponent();
gamemodeDropdown.setText("%createWorldScreen.gameMode.personal");
gamemodeDropdown.setOptions(new ArrayList<>());
for (GameMode gamemode : GameMode.values()) {
gamemodeDropdown.addOption(LocaleUtils.getLocaleString("selectWorld.gameMode." + gamemode.name().toLowerCase(), language), session.getGameMode() == gamemode);
}
builder.addComponent(gamemodeDropdown);
DropdownComponent difficultyDropdown = new DropdownComponent();
difficultyDropdown.setText("%options.difficulty");
difficultyDropdown.setOptions(new ArrayList<>());
for (Difficulty difficulty : Difficulty.values()) {
difficultyDropdown.addOption("%options.difficulty." + difficulty.name().toLowerCase(), session.getWorldCache().getDifficulty() == difficulty);
}
builder.addComponent(difficultyDropdown);
}
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
builder.addComponent(new LabelComponent(LanguageUtils.getPlayerLocaleString("geyser.settings.title.game_rules", language)));
for (GameRule gamerule : GameRule.values()) {
if (gamerule.equals(GameRule.UNKNOWN)) {
continue;
}
// Add the relevant form item based on the gamerule type
if (Boolean.class.equals(gamerule.getType())) {
builder.addComponent(new ToggleComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), GeyserConnector.getInstance().getWorldManager().getGameRuleBool(session, gamerule)));
} else if (Integer.class.equals(gamerule.getType())) {
builder.addComponent(new InputComponent(LocaleUtils.getLocaleString("gamerule." + gamerule.getJavaID(), language), "", String.valueOf(GeyserConnector.getInstance().getWorldManager().getGameRuleInt(session, gamerule))));
}
}
}
session.setSettingsForm(builder.build());
}
/**
* Handle the settings form response
*
* @param session The session that sent the response
* @param response The response string to parse
* @return True if the form was parsed correctly, false if not
*/
public static boolean handleSettingsForm(GeyserSession session, String response) {
CustomFormWindow settingsForm = session.getSettingsForm();
settingsForm.setResponse(response);
CustomFormResponse settingsResponse = (CustomFormResponse) settingsForm.getResponse();
int offset = 0;
offset++; // Client settings title
session.getWorldCache().setShowCoordinates(settingsResponse.getToggleResponses().get(offset));
offset++;
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.server")) {
offset++; // Server settings title
GameMode gameMode = GameMode.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
if (gameMode != null && gameMode != session.getGameMode()) {
session.getConnector().getWorldManager().setPlayerGameMode(session, gameMode);
}
offset++;
Difficulty difficulty = Difficulty.values()[settingsResponse.getDropdownResponses().get(offset).getElementID()];
if (difficulty != null && difficulty != session.getWorldCache().getDifficulty()) {
session.getConnector().getWorldManager().setDifficulty(session, difficulty);
}
offset++;
}
if (session.getOpPermissionLevel() >= 2 || session.hasPermission("geyser.settings.gamerules")) {
offset++; // Game rule title
for (GameRule gamerule : GameRule.values()) {
if (gamerule.equals(GameRule.UNKNOWN)) {
continue;
}
if (Boolean.class.equals(gamerule.getType())) {
Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue();
if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) {
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
}
} else if (Integer.class.equals(gamerule.getType())) {
int value = Integer.parseInt(settingsResponse.getInputResponses().get(offset));
if (value != session.getConnector().getWorldManager().getGameRuleInt(session, gamerule)) {
session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value);
}
}
offset++;
}
}
return true;
}
}

View file

@ -35,6 +35,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.connector.common.AuthType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.Entity;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.BedrockClientData;
@ -93,6 +94,15 @@ public class SkinUtils {
ImageData.of(capeData), geometryData, "", true, false, !capeId.equals(SkinProvider.EMPTY_CAPE.getCapeId()), capeId, skinId
);
// This attempts to find the xuid of the player so profile images show up for xbox accounts
String xuid = "";
for (GeyserSession player : GeyserConnector.getInstance().getPlayers()) {
if (player.getPlayerEntity().getUuid().equals(uuid)) {
xuid = player.getAuthData().getXboxUUID();
break;
}
}
PlayerListPacket.Entry entry;
// If we are building a PlayerListEntry for our own session we use our AuthData UUID instead of the Java UUID
@ -102,11 +112,11 @@ public class SkinUtils {
} else {
entry = new PlayerListPacket.Entry(uuid);
}
entry.setName(username);
entry.setEntityId(geyserId);
entry.setSkin(serializedSkin);
entry.setXuid("");
entry.setXuid(xuid);
entry.setPlatformChatId("");
entry.setTeacher(false);
entry.setTrustedSkin(true);