Files
mc/server/session/session_list.go
TarnaWijaya 26ed99fda6
Build and deploy / Build (push) Has been cancelled
Build and deploy / Update Contributors (push) Has been cancelled
Build and deploy / Deploy (push) Has been cancelled
up3
2026-07-09 08:33:57 +08:00

150 lines
4.1 KiB
Go

package session
import (
"slices"
"sync"
"github.com/df-mc/dragonfly/server/internal/sliceutil"
"github.com/df-mc/dragonfly/server/player/skin"
"github.com/df-mc/dragonfly/server/world"
"github.com/google/uuid"
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
)
var sessions = new(sessionList)
type sessionList struct {
mu sync.Mutex
s []*Session
}
func (l *sessionList) Add(s *Session) {
l.mu.Lock()
defer l.mu.Unlock()
for _, other := range l.s {
// Show all sessions to the new session and the new session to all
// existing sessions.
l.sendSessionTo(s, other)
l.sendSessionTo(other, s)
}
// Show the new session to itself.
l.sendSessionTo(s, s)
l.s = append(l.s, s)
}
func (l *sessionList) Remove(s *Session, entity world.Entity) {
l.mu.Lock()
removedFrom := slices.Clone(l.s)
for _, other := range l.s {
l.unsendSessionFrom(s, other)
}
l.s = sliceutil.DeleteVal(l.s, s)
l.mu.Unlock()
if entity == nil {
return
}
for _, other := range removedFrom {
if other.viewLayer != nil {
other.viewLayer.Remove(entity)
}
}
}
func (l *sessionList) Lookup(id uuid.UUID) (*Session, bool) {
l.mu.Lock()
defer l.mu.Unlock()
if index := slices.IndexFunc(l.s, func(session *Session) bool {
return session.ent.UUID() == id
}); index != -1 {
return l.s[index], true
}
return nil, false
}
func (l *sessionList) sendSessionTo(s, to *Session) {
runtimeID := uint64(selfEntityRuntimeID)
to.entityMutex.Lock()
if s != to {
to.currentEntityRuntimeID += 1
runtimeID = to.currentEntityRuntimeID
}
to.entityRuntimeIDs[s.ent] = runtimeID
to.entities[runtimeID] = s.ent
to.entityMutex.Unlock()
to.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionAdd,
Entries: []protocol.PlayerListEntry{{
UUID: s.ent.UUID(),
EntityUniqueID: int64(runtimeID),
Username: s.conn.IdentityData().DisplayName,
XUID: s.conn.IdentityData().XUID,
Skin: skinToProtocol(s.joinSkin),
}},
})
}
func (l *sessionList) unsendSessionFrom(s, from *Session) {
from.entityMutex.Lock()
delete(from.entities, from.entityRuntimeIDs[s.ent])
delete(from.entityRuntimeIDs, s.ent)
from.entityMutex.Unlock()
from.writePacket(&packet.PlayerList{
ActionType: packet.PlayerListActionRemove,
Entries: []protocol.PlayerListEntry{{UUID: s.ent.UUID()}},
})
}
// skinToProtocol converts a skin to its protocol representation.
func skinToProtocol(s skin.Skin) protocol.Skin {
var animations []protocol.SkinAnimation
for _, animation := range s.Animations {
protocolAnim := protocol.SkinAnimation{
ImageWidth: uint32(animation.Bounds().Max.X),
ImageHeight: uint32(animation.Bounds().Max.Y),
ImageData: animation.Pix,
FrameCount: float32(animation.FrameCount),
}
switch animation.Type() {
case skin.AnimationHead:
protocolAnim.AnimationType = protocol.SkinAnimationHead
case skin.AnimationBody32x32:
protocolAnim.AnimationType = protocol.SkinAnimationBody32x32
case skin.AnimationBody128x128:
protocolAnim.AnimationType = protocol.SkinAnimationBody128x128
}
protocolAnim.ExpressionType = uint32(animation.AnimationExpression)
animations = append(animations, protocolAnim)
}
fullID := s.FullID
if fullID == "" {
fullID = uuid.New().String()
}
return protocol.Skin{
PlayFabID: s.PlayFabID,
SkinID: uuid.New().String(),
SkinResourcePatch: s.ModelConfig.Encode(),
SkinImageWidth: uint32(s.Bounds().Max.X),
SkinImageHeight: uint32(s.Bounds().Max.Y),
SkinData: s.Pix,
CapeImageWidth: uint32(s.Cape.Bounds().Max.X),
CapeImageHeight: uint32(s.Cape.Bounds().Max.Y),
CapeData: s.Cape.Pix,
SkinGeometry: s.Model,
PersonaSkin: s.Persona,
CapeID: uuid.New().String(),
FullID: fullID,
Animations: animations,
Trusted: true,
OverrideAppearance: true,
GeometryDataEngineVersion: []byte(protocol.CurrentVersion),
}
}