diff --git a/server/player/conf.go b/server/player/conf.go index 8f2ee9af27..a39f2e7315 100644 --- a/server/player/conf.go +++ b/server/player/conf.go @@ -9,6 +9,7 @@ import ( "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/item/inventory" + "github.com/df-mc/dragonfly/server/player/permission" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/session" "github.com/df-mc/dragonfly/server/world" @@ -27,6 +28,9 @@ type Config struct { Locale language.Tag GameMode world.GameMode + // PermissionLevel is the permission level the client displays for itself. + PermissionLevel permission.Level + Position mgl64.Vec3 Rotation cube.Rotation Velocity mgl64.Vec3 @@ -72,6 +76,7 @@ func (cfg Config) Apply(data *world.EntityData) { mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}, heldSlot: &slot, gameMode: conf.GameMode, + permission: conf.PermissionLevel, skin: conf.Skin, enchantSeed: conf.EnchantmentSeed, s: conf.Session, diff --git a/server/player/permission/level.go b/server/player/permission/level.go new file mode 100644 index 0000000000..0127464f51 --- /dev/null +++ b/server/player/permission/level.go @@ -0,0 +1,34 @@ +package permission + +import ( + "github.com/sandertv/gophertunnel/minecraft/protocol" + "github.com/sandertv/gophertunnel/minecraft/protocol/packet" +) + +// Level represents the permission level a client displays for itself. It only decides what the client shows +// and allows locally, such as the command button in the chat window: the server does not use it to authorise +// anything. Whether a player may run a command is decided by cmd.Allower alone. +type Level struct { + level +} + +type level uint8 + +// Member is the level of an ordinary client. It is the level a player has unless another one is set. +func Member() Level { + return Level{0} +} + +// Operator is the level of a client that vanilla considers an operator. +func Operator() Level { + return Level{1} +} + +// Permissions returns the player permission level and command permission level sent to a client for the +// Level. +func (l level) Permissions() (byte, byte) { + if l == 1 { + return packet.PermissionLevelOperator, protocol.CommandPermissionLevelGameDirectors + } + return packet.PermissionLevelMember, protocol.CommandPermissionLevelAny +} diff --git a/server/player/player.go b/server/player/player.go index fd0f5534bb..eb0b4fc60a 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -28,6 +28,7 @@ import ( "github.com/df-mc/dragonfly/server/player/form" "github.com/df-mc/dragonfly/server/player/hud" "github.com/df-mc/dragonfly/server/player/input" + "github.com/df-mc/dragonfly/server/player/permission" "github.com/df-mc/dragonfly/server/player/scoreboard" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/player/title" @@ -48,10 +49,11 @@ type playerData struct { absorptionHealth float64 scale float64 - gameMode world.GameMode - skin skin.Skin - s *session.Session - h Handler + gameMode world.GameMode + permission permission.Level + skin skin.Skin + s *session.Session + h Handler inv, offHand, enderChest, ui *inventory.Inventory armour *inventory.Armour @@ -1546,6 +1548,25 @@ func (p *Player) GameMode() world.GameMode { return p.gameMode } +// SetPermissionLevel changes the permission level the Player's client displays +// for itself. It unlocks vanilla functionality that the client restricts to a +// permission level, such as the command button in the chat window, but does not +// authorise the Player to do anything by itself: cmd.Allower decides which +// commands a Player may run. +func (p *Player) SetPermissionLevel(level permission.Level) { + if p.permission == level { + return + } + p.permission = level + p.session().SendAbilities(p) +} + +// PermissionLevel returns the permission level the Player's client displays for +// itself, as set by SetPermissionLevel. +func (p *Player) PermissionLevel() permission.Level { + return p.permission +} + // HasCooldown returns true if the item passed has an active cooldown, meaning it currently cannot be used again. If the // world.Item passed is nil, HasCooldown always returns false. func (p *Player) HasCooldown(item world.Item) bool { @@ -3388,6 +3409,7 @@ func (p *Player) Data() Config { Name: p.nameTag, Locale: p.locale, GameMode: p.gameMode, + PermissionLevel: p.permission, Position: p.Position(), Rotation: p.Rotation(), Velocity: p.Velocity(), diff --git a/server/server.go b/server/server.go index 574515bc58..3e733ed19b 100644 --- a/server/server.go +++ b/server/server.go @@ -474,6 +474,8 @@ func (srv *Server) finaliseConn(ctx context.Context, conn session.Conn, l Listen data.Dimension = int32(dim) data.Yaw, data.Pitch = float32(d.Rotation.Yaw()), float32(d.Rotation.Pitch()) + data.PlayerPermissions, _ = d.PermissionLevel.Permissions() + data.EmoteChatMuted = srv.conf.MuteEmoteChat if err := conn.StartGameContext(ctx, data); err != nil { diff --git a/server/session/controllable.go b/server/session/controllable.go index d5386931d2..7a9b30b57b 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -12,6 +12,7 @@ import ( "github.com/df-mc/dragonfly/server/player/form" "github.com/df-mc/dragonfly/server/player/hud" "github.com/df-mc/dragonfly/server/player/input" + "github.com/df-mc/dragonfly/server/player/permission" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" @@ -51,6 +52,7 @@ type Controllable interface { Chat(msg ...any) ExecuteCommand(commandLine string) GameMode() world.GameMode + PermissionLevel() permission.Level SetGameMode(mode world.GameMode) Effects() []effect.Effect diff --git a/server/session/player.go b/server/session/player.go index 3345f43415..6e572e5f7c 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -545,10 +545,11 @@ func (s *Session) SendAbilities(c Controllable) { if mode.AllowsInteraction() { abilities |= protocol.AbilityDoorsAndSwitches | protocol.AbilityOpenContainers | protocol.AbilityAttackPlayers | protocol.AbilityAttackMobs } + perm, cmdPerm := c.PermissionLevel().Permissions() s.writePacket(&packet.UpdateAbilities{AbilityData: protocol.AbilityData{ EntityUniqueID: selfEntityRuntimeID, - PlayerPermissions: packet.PermissionLevelMember, - CommandPermissions: protocol.CommandPermissionLevelAny, + PlayerPermissions: perm, + CommandPermissions: cmdPerm, Layers: []protocol.AbilityLayer{ { Type: protocol.AbilityLayerTypeBase,