Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi bossbar support #902

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,17 +341,22 @@ func (p *Player) RemoveScoreboard() {
p.session().RemoveScoreboard()
}

// SendBossBar sends a boss bar to the player, so that it will be shown indefinitely at the top of the
// player's screen.
// The boss bar may be removed by calling Player.RemoveBossBar().
func (p *Player) SendBossBar(bar bossbar.BossBar) {
p.session().SendBossBar(bar.Text(), bar.Colour().Uint8(), bar.HealthPercentage())
// SendBossBar sends a boss bar to the player on the specified channel, so that it will be shown indefinitely at the top of the
// player's screen. The boss bar can support multiple channels.
// The boss bar may be removed by calling Player.RemoveBossBar(channel).
func (p *Player) SendBossBar(channel int, bar bossbar.BossBar) {
p.session().SendBossBar(channel, bar.Text(), bar.Colour().Uint8(), bar.HealthPercentage())
}

// RemoveBossBar removes any boss bar currently active on the player's screen. If no boss bar is currently
// present, nothing happens.
func (p *Player) RemoveBossBar() {
p.session().RemoveBossBar()
// RemoveBossBar removes the boss bar currently active on the specified channel on the player's screen.
// If no boss bar is currently active on the given channel, nothing happens.
func (p *Player) RemoveBossBar(channel int) {
p.session().RemoveBossBar(channel)
}

// BossBarChannelIDs returns a list of all channel IDs that are currently active on the player's screen.
func (p *Player) BossBarChannelIDs() []int {
return p.session().BossBarChannelIDs()
}

// Chat writes a message in the global chat (chat.Global). The message is prefixed with the name of the
Expand Down
40 changes: 23 additions & 17 deletions server/session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type Session struct {
joinMessage, quitMessage string

closeBackground chan struct{}

bossBarChannelRuntimeIDs map[int]uint64
}

// Conn represents a connection that packets are read from and written to by a Session. In addition, it holds some
Expand Down Expand Up @@ -148,23 +150,24 @@ func New(conn Conn, maxChunkRadius int, log Logger, joinMessage, quitMessage str

s := &Session{}
*s = Session{
openChunkTransactions: make([]map[uint64]struct{}, 0, 8),
closeBackground: make(chan struct{}),
ui: inventory.New(54, s.handleInterfaceUpdate),
handlers: map[uint32]packetHandler{},
entityRuntimeIDs: map[world.Entity]uint64{},
entities: map[uint64]world.Entity{},
hiddenEntities: map[world.Entity]struct{}{},
blobs: map[uint64][]byte{},
chunkRadius: int32(r),
maxChunkRadius: int32(maxChunkRadius),
conn: conn,
log: log,
currentEntityRuntimeID: 1,
heldSlot: atomic.NewUint32(0),
joinMessage: joinMessage,
quitMessage: quitMessage,
openedWindow: *atomic.NewValue(inventory.New(1, nil)),
openChunkTransactions: make([]map[uint64]struct{}, 0, 8),
closeBackground: make(chan struct{}),
ui: inventory.New(54, s.handleInterfaceUpdate),
handlers: map[uint32]packetHandler{},
entityRuntimeIDs: map[world.Entity]uint64{},
entities: map[uint64]world.Entity{},
hiddenEntities: map[world.Entity]struct{}{},
blobs: map[uint64][]byte{},
chunkRadius: int32(r),
maxChunkRadius: int32(maxChunkRadius),
conn: conn,
log: log,
currentEntityRuntimeID: 1,
heldSlot: atomic.NewUint32(0),
joinMessage: joinMessage,
quitMessage: quitMessage,
openedWindow: *atomic.NewValue(inventory.New(1, nil)),
bossBarChannelRuntimeIDs: map[int]uint64{},
}

s.registerHandlers()
Expand Down Expand Up @@ -526,6 +529,9 @@ func (s *Session) sendAvailableEntities(w *world.World) {
for _, t := range w.EntityRegistry().Types() {
identifiers = append(identifiers, actorIdentifier{ID: t.EncodeEntity()})
}
// TODO: "minecraft:slime" is used to send the boss bar. This should not be hardcoded.
// Remove this when slime is supported.
identifiers = append(identifiers, actorIdentifier{ID: "minecraft:slime"})
serializedEntityData, err := nbt.Marshal(map[string]any{"idlist": identifiers})
if err != nil {
panic("should never happen")
Expand Down
51 changes: 43 additions & 8 deletions server/session/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,60 @@ func (s *Session) RemoveScoreboard() {
s.currentLines.Store([]string{})
}

// SendBossBar sends a boss bar to the player with the text passed and the health percentage of the bar.
// SendBossBar removes any boss bar that might be active before sending the new one.
func (s *Session) SendBossBar(text string, colour uint8, healthPercentage float64) {
s.RemoveBossBar()
// SendBossBar sends a boss bar to the player on the specified channel with the provided text and health percentage.
// If a boss bar is already active on the given channel, it is removed before the new one is sent.
func (s *Session) SendBossBar(channel int, text string, colour uint8, healthPercentage float64) {
s.RemoveBossBar(channel)
s.currentEntityRuntimeID += 1
runtimeID := s.currentEntityRuntimeID
s.bossBarChannelRuntimeIDs[channel] = runtimeID

m := protocol.EntityMetadata{}
m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagSilent)
m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagNoAI)
m.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagFireImmune)
m[protocol.EntityDataKeyName] = ""
m[protocol.EntityDataKeyScale] = float32(0)
m[protocol.EntityDataKeyWidth] = float32(0)
m[protocol.EntityDataKeyHeight] = float32(0)
m[protocol.EntityDataKeyLeashHolder] = int64(-1)

s.writePacket(&packet.AddActor{
EntityUniqueID: int64(runtimeID),
EntityRuntimeID: runtimeID,
EntityType: "minecraft:slime", // TODO: o not hardcode
EntityMetadata: m,
})
s.writePacket(&packet.BossEvent{
BossEntityUniqueID: selfEntityRuntimeID,
BossEntityUniqueID: int64(runtimeID),
EventType: packet.BossEventShow,
BossBarTitle: text,
HealthPercentage: float32(healthPercentage),
Colour: uint32(colour),
})
}

// RemoveBossBar removes any boss bar currently active on the player's screen.
func (s *Session) RemoveBossBar() {
// RemoveBossBar removes the boss bar currently active on the specified channel on the player's screen.
func (s *Session) RemoveBossBar(channel int) {
runtimeID, ok := s.bossBarChannelRuntimeIDs[channel]
if !ok {
return
}
s.writePacket(&packet.BossEvent{
BossEntityUniqueID: selfEntityRuntimeID,
BossEntityUniqueID: int64(runtimeID),
EventType: packet.BossEventHide,
})
s.writePacket(&packet.RemoveActor{EntityUniqueID: int64(runtimeID)})
}

// BossBarChannelIDs returns a list of all boss bar channel IDs that are currently active on the player's screen.
func (s *Session) BossBarChannelIDs() []int {
var ids []int
for id := range s.bossBarChannelRuntimeIDs {
ids = append(ids, id)
}
return ids
}

const tickLength = time.Second / 20
Expand Down
Loading