diff --git a/server/player/player.go b/server/player/player.go index 47ba23660..86b38eda4 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -3,6 +3,7 @@ package player import ( "fmt" "github.com/df-mc/dragonfly/server/player/debug" + "image/color" "math" "math/rand/v2" "net" @@ -403,6 +404,16 @@ func (p *Player) CloseForm() { p.session().CloseForm() } +// ShowLocatorBar enables the vanilla locator bar for the player. +func (p *Player) ShowLocatorBar() { + p.session().EnableLocatorBar(true) +} + +// HideLocatorBar disables the vanilla locator bar for the player. +func (p *Player) HideLocatorBar() { + p.session().EnableLocatorBar(false) +} + // ShowCoordinates enables the vanilla coordinates for the player. func (p *Player) ShowCoordinates() { p.session().EnableCoordinates(true) @@ -2188,6 +2199,16 @@ func (p *Player) Collect(s item.Stack) (int, bool) { return added, true } +// Colour returns the player's colour in the locator bar. +func (p *Player) Colour() color.RGBA { + return p.session().Colour() +} + +// SetColour changes the player's colour in the locator bar. +func (p *Player) SetColour(colour color.RGBA) { + p.session().SetColour(colour) +} + // Experience returns the amount of experience the player has. func (p *Player) Experience() int { return p.experience.Experience() diff --git a/server/session/player.go b/server/session/player.go index 6908b2dae..20e2e396f 100644 --- a/server/session/player.go +++ b/server/session/player.go @@ -3,8 +3,10 @@ package session import ( "encoding/json" "fmt" + "github.com/cespare/xxhash/v2" "github.com/df-mc/dragonfly/server/player/debug" "github.com/go-gl/mathgl/mgl32" + "golang.org/x/exp/rand" "image/color" "maps" "math" @@ -578,6 +580,12 @@ func (s *Session) sendGameRules(gameRules []protocol.GameRule) { s.writePacket(&packet.GameRulesChanged{GameRules: gameRules}) } +// EnableLocatorBar will either enable or disable locator bar for the player depending on the value given. +func (s *Session) EnableLocatorBar(enable bool) { + //noinspection SpellCheckingInspection + s.sendGameRules([]protocol.GameRule{{Name: "locatorbar", Value: enable}}) +} + // EnableCoordinates will either enable or disable coordinates for the player depending on the value given. func (s *Session) EnableCoordinates(enable bool) { //noinspection SpellCheckingInspection @@ -590,6 +598,21 @@ func (s *Session) EnableInstantRespawn(enable bool) { s.sendGameRules([]protocol.GameRule{{Name: "doimmediaterespawn", Value: enable}}) } +// Colour returns the colour of player. Colour is used to indicate a player in the locator bar. +func (s *Session) Colour() color.RGBA { + colour := s.colour.Load() + if colour == nil { + return color.RGBA{A: 0xFF} + } + return *colour +} + +// SetColour sets the player colour. Colour is used to indicate a player in the locator bar. +func (s *Session) SetColour(colour color.RGBA) { + s.colour.Store(&colour) + sessions.resendList(s) +} + // HandleInventories starts handling the inventories of the Controllable entity of the session. It sends packets when // slots in the inventory are changed. func (s *Session) HandleInventories(tx *world.Tx, c Controllable, inv, offHand, enderChest, ui *inventory.Inventory, armour *inventory.Armour, heldSlot *uint32) { @@ -1053,6 +1076,17 @@ func protocolToSkin(sk protocol.Skin) (s skin.Skin, err error) { return } +// randomColour returns random colour based on displayName's hash. +func randomColour(displayName string) color.RGBA { + r := rand.New(rand.NewSource(xxhash.Sum64String(displayName))) + return color.RGBA{ + R: byte(r.Int31n(256)), + G: byte(r.Int31n(256)), + B: byte(r.Int31n(256)), + A: 0xFF, + } +} + // gameTypeFromMode returns the game type ID from the game mode passed. func gameTypeFromMode(mode world.GameMode) int32 { if mode.AllowsFlying() && mode.CreativeInventory() { diff --git a/server/session/session.go b/server/session/session.go index 76dff2bfb..bdf0c5e67 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/df-mc/dragonfly/server/player/debug" + "image/color" "io" "log/slog" "net" @@ -91,6 +92,8 @@ type Session struct { debugShapesAdd chan debug.Shape debugShapesRemove chan int + colour atomic.Pointer[color.RGBA] + closeBackground chan struct{} } @@ -176,6 +179,9 @@ func (conf Config) New(conn Conn) *Session { debugShapesAdd: make(chan debug.Shape, 256), debugShapesRemove: make(chan int, 256), } + colour := randomColour(conn.IdentityData().DisplayName) + s.colour.Store(&colour) + s.openedWindow.Store(inventory.New(1, nil)) s.openedPos.Store(&cube.Pos{}) diff --git a/server/session/session_list.go b/server/session/session_list.go index 12c869081..b69f71dcf 100644 --- a/server/session/session_list.go +++ b/server/session/session_list.go @@ -42,6 +42,16 @@ func (l *sessionList) Remove(s *Session) { l.s = sliceutil.DeleteVal(l.s, s) } +func (l *sessionList) resendList(s *Session) { + l.mu.Lock() + defer l.mu.Unlock() + + for _, other := range l.s { + l.unsendSessionFrom(s, other) + l.sendSessionTo(s, other) + } +} + func (l *sessionList) Lookup(id uuid.UUID) (*Session, bool) { l.mu.Lock() defer l.mu.Unlock() @@ -74,6 +84,7 @@ func (l *sessionList) sendSessionTo(s, to *Session) { Username: s.conn.IdentityData().DisplayName, XUID: s.conn.IdentityData().XUID, Skin: skinToProtocol(s.joinSkin), + PlayerColour: s.Colour(), }}, }) }