1441 lines
43 KiB
Go
1441 lines
43 KiB
Go
|
|
package session
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"fmt"
|
||
|
|
"image/color"
|
||
|
|
"math/rand/v2"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/df-mc/dragonfly/server/block"
|
||
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
||
|
|
"github.com/df-mc/dragonfly/server/entity"
|
||
|
|
"github.com/df-mc/dragonfly/server/entity/effect"
|
||
|
|
"github.com/df-mc/dragonfly/server/internal/nbtconv"
|
||
|
|
"github.com/df-mc/dragonfly/server/item"
|
||
|
|
"github.com/df-mc/dragonfly/server/item/inventory"
|
||
|
|
"github.com/df-mc/dragonfly/server/world"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/particle"
|
||
|
|
"github.com/df-mc/dragonfly/server/world/sound"
|
||
|
|
"github.com/go-gl/mathgl/mgl32"
|
||
|
|
"github.com/go-gl/mathgl/mgl64"
|
||
|
|
"github.com/google/uuid"
|
||
|
|
"github.com/sandertv/gophertunnel/minecraft/nbt"
|
||
|
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||
|
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||
|
|
)
|
||
|
|
|
||
|
|
// NetworkEncodeableEntity is a world.EntityType where the save ID and network
|
||
|
|
// ID are not the same.
|
||
|
|
type NetworkEncodeableEntity interface {
|
||
|
|
// NetworkEncodeEntity returns the network type ID of the entity. This is
|
||
|
|
// NOT the save ID.
|
||
|
|
NetworkEncodeEntity() string
|
||
|
|
}
|
||
|
|
|
||
|
|
// OffsetEntity is a world.EntityType that has an additional offset when sent
|
||
|
|
// over network. This is mostly the case for older entities such as players and
|
||
|
|
// TNT.
|
||
|
|
type OffsetEntity interface {
|
||
|
|
NetworkOffset() float64
|
||
|
|
}
|
||
|
|
|
||
|
|
// entityHidden checks if a world.Entity is being explicitly hidden from the Session.
|
||
|
|
func (s *Session) entityHidden(e world.Entity) bool {
|
||
|
|
s.entityMutex.RLock()
|
||
|
|
_, ok := s.hiddenEntities[e.H().UUID()]
|
||
|
|
s.entityMutex.RUnlock()
|
||
|
|
return ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntity ...
|
||
|
|
func (s *Session) ViewEntity(e world.Entity) {
|
||
|
|
if e.H() == s.ent {
|
||
|
|
s.ViewEntityState(e)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if s.entityHidden(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var runtimeID uint64
|
||
|
|
|
||
|
|
_, controllable := e.(Controllable)
|
||
|
|
|
||
|
|
s.entityMutex.Lock()
|
||
|
|
if id, ok := s.entityRuntimeIDs[e.H()]; ok && controllable {
|
||
|
|
runtimeID = id
|
||
|
|
} else {
|
||
|
|
s.currentEntityRuntimeID += 1
|
||
|
|
runtimeID = s.currentEntityRuntimeID
|
||
|
|
s.entityRuntimeIDs[e.H()] = runtimeID
|
||
|
|
s.entities[runtimeID] = e.H()
|
||
|
|
}
|
||
|
|
s.entityMutex.Unlock()
|
||
|
|
|
||
|
|
yaw, pitch := e.Rotation().Elem()
|
||
|
|
metadata := s.entityMetadata(e)
|
||
|
|
|
||
|
|
id := e.H().Type().EncodeEntity()
|
||
|
|
switch v := e.(type) {
|
||
|
|
case Controllable:
|
||
|
|
_, actualPlayer := sessions.Lookup(v.UUID())
|
||
|
|
if !actualPlayer {
|
||
|
|
s.writePacket(&packet.PlayerList{ActionType: packet.PlayerListActionAdd, Entries: []protocol.PlayerListEntry{{
|
||
|
|
UUID: v.UUID(),
|
||
|
|
EntityUniqueID: int64(runtimeID),
|
||
|
|
Username: v.Name(),
|
||
|
|
Skin: skinToProtocol(v.Skin()),
|
||
|
|
}}})
|
||
|
|
}
|
||
|
|
|
||
|
|
s.writePacket(&packet.AddPlayer{
|
||
|
|
EntityMetadata: metadata,
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
GameType: gameTypeFromMode(v.GameMode()),
|
||
|
|
HeadYaw: float32(yaw),
|
||
|
|
Pitch: float32(pitch),
|
||
|
|
Position: vec64To32(e.Position()),
|
||
|
|
UUID: v.UUID(),
|
||
|
|
Username: v.Name(),
|
||
|
|
Yaw: float32(yaw),
|
||
|
|
AbilityData: protocol.AbilityData{
|
||
|
|
EntityUniqueID: int64(runtimeID),
|
||
|
|
Layers: []protocol.AbilityLayer{{
|
||
|
|
Type: protocol.AbilityLayerTypeBase,
|
||
|
|
Abilities: protocol.AbilityCount - 1,
|
||
|
|
}},
|
||
|
|
},
|
||
|
|
})
|
||
|
|
if !actualPlayer {
|
||
|
|
s.writePacket(&packet.PlayerList{ActionType: packet.PlayerListActionRemove, Entries: []protocol.PlayerListEntry{{
|
||
|
|
UUID: v.UUID(),
|
||
|
|
}}})
|
||
|
|
} else {
|
||
|
|
s.ViewSkin(e)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
case *entity.Ent:
|
||
|
|
switch e.H().Type() {
|
||
|
|
case entity.ItemType:
|
||
|
|
s.writePacket(&packet.AddItemActor{
|
||
|
|
EntityUniqueID: int64(runtimeID),
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
Item: instanceFromItem(s.br, v.Behaviour().(*entity.ItemBehaviour).Item()),
|
||
|
|
Position: vec64To32(v.Position()),
|
||
|
|
Velocity: vec64To32(v.Velocity()),
|
||
|
|
EntityMetadata: metadata,
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case entity.TextType:
|
||
|
|
metadata[protocol.EntityDataKeyVariant] = int32(s.br.BlockRuntimeID(block.Air{}))
|
||
|
|
case entity.FallingBlockType:
|
||
|
|
metadata[protocol.EntityDataKeyVariant] = int32(s.br.BlockRuntimeID(v.Behaviour().(*entity.FallingBlockBehaviour).Block()))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if v, ok := e.H().Type().(NetworkEncodeableEntity); ok {
|
||
|
|
id = v.NetworkEncodeEntity()
|
||
|
|
}
|
||
|
|
|
||
|
|
var vel mgl64.Vec3
|
||
|
|
if v, ok := e.(interface{ Velocity() mgl64.Vec3 }); ok {
|
||
|
|
vel = v.Velocity()
|
||
|
|
}
|
||
|
|
|
||
|
|
s.writePacket(&packet.AddActor{
|
||
|
|
EntityUniqueID: int64(runtimeID),
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
EntityType: id,
|
||
|
|
EntityMetadata: metadata,
|
||
|
|
Position: vec64To32(e.Position()),
|
||
|
|
Velocity: vec64To32(vel),
|
||
|
|
Pitch: float32(pitch),
|
||
|
|
Yaw: float32(yaw),
|
||
|
|
HeadYaw: float32(yaw),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityGameMode ...
|
||
|
|
func (s *Session) ViewEntityGameMode(e world.Entity) {
|
||
|
|
if s.entityHidden(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
c, ok := e.(Controllable)
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.UpdatePlayerGameType{
|
||
|
|
GameType: gameTypeFromMode(c.GameMode()),
|
||
|
|
PlayerUniqueID: int64(s.entityRuntimeID(c)),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// HideEntity ...
|
||
|
|
func (s *Session) HideEntity(e world.Entity) {
|
||
|
|
if s.entityRuntimeID(e) == selfEntityRuntimeID {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
s.entityMutex.Lock()
|
||
|
|
id, ok := s.entityRuntimeIDs[e.H()]
|
||
|
|
if _, controllable := e.(Controllable); !controllable {
|
||
|
|
delete(s.entityRuntimeIDs, e.H())
|
||
|
|
delete(s.entities, id)
|
||
|
|
}
|
||
|
|
s.entityMutex.Unlock()
|
||
|
|
if !ok {
|
||
|
|
// The entity was already removed some other way. We don't need to send a packet.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.RemoveActor{EntityUniqueID: int64(id)})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityMovement ...
|
||
|
|
func (s *Session) ViewEntityMovement(e world.Entity, pos mgl64.Vec3, rot cube.Rotation, onGround bool) {
|
||
|
|
id := s.entityRuntimeID(e)
|
||
|
|
if (id == selfEntityRuntimeID && s.moving) || s.entityHidden(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
flags := byte(0)
|
||
|
|
if onGround {
|
||
|
|
flags |= packet.MoveFlagOnGround
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.MoveActorAbsolute{
|
||
|
|
EntityRuntimeID: id,
|
||
|
|
Position: vec64To32(pos.Add(entityOffset(e))),
|
||
|
|
Rotation: vec64To32(mgl64.Vec3{rot.Pitch(), rot.Yaw(), rot.Yaw()}),
|
||
|
|
Flags: flags,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityVelocity ...
|
||
|
|
func (s *Session) ViewEntityVelocity(e world.Entity, velocity mgl64.Vec3) {
|
||
|
|
if s.entityHidden(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.SetActorMotion{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
Velocity: vec64To32(velocity),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// entityOffset returns the offset that entities have client-side.
|
||
|
|
func entityOffset(e world.Entity) mgl64.Vec3 {
|
||
|
|
if offset, ok := e.H().Type().(OffsetEntity); ok {
|
||
|
|
return mgl64.Vec3{0, offset.NetworkOffset()}
|
||
|
|
}
|
||
|
|
return mgl64.Vec3{}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewTime ...
|
||
|
|
func (s *Session) ViewTime(time int) {
|
||
|
|
s.writePacket(&packet.SetTime{Time: int32(time)})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewTimeCycle ...
|
||
|
|
func (s *Session) ViewTimeCycle(doDayLightCycle bool) {
|
||
|
|
s.sendGameRules([]protocol.GameRule{{Name: "dodaylightcycle", Value: doDayLightCycle}})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityTeleport ...
|
||
|
|
func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) {
|
||
|
|
id := s.entityRuntimeID(e)
|
||
|
|
if s.entityHidden(e) {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
yaw, pitch := e.Rotation().Elem()
|
||
|
|
if id == selfEntityRuntimeID {
|
||
|
|
s.teleportPos.Store(&position)
|
||
|
|
}
|
||
|
|
|
||
|
|
s.writePacket(&packet.SetActorMotion{EntityRuntimeID: id})
|
||
|
|
if _, ok := e.(Controllable); ok {
|
||
|
|
s.writePacket(&packet.MovePlayer{
|
||
|
|
EntityRuntimeID: id,
|
||
|
|
Position: vec64To32(position.Add(entityOffset(e))),
|
||
|
|
Pitch: float32(pitch),
|
||
|
|
Yaw: float32(yaw),
|
||
|
|
HeadYaw: float32(yaw),
|
||
|
|
Mode: packet.MoveModeTeleport,
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.MoveActorAbsolute{
|
||
|
|
EntityRuntimeID: id,
|
||
|
|
Position: vec64To32(position.Add(entityOffset(e))),
|
||
|
|
Rotation: vec64To32(mgl64.Vec3{pitch, yaw, yaw}),
|
||
|
|
Flags: packet.MoveFlagTeleport,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityItems ...
|
||
|
|
func (s *Session) ViewEntityItems(e world.Entity) {
|
||
|
|
runtimeID := s.entityRuntimeID(e)
|
||
|
|
if runtimeID == selfEntityRuntimeID || s.entityHidden(e) {
|
||
|
|
// Don't view the items of the entity if the entity is the Controllable entity of the session.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
c, ok := e.(item.Carrier)
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
mainHand, offHand := c.HeldItems()
|
||
|
|
|
||
|
|
// Show the main hand item.
|
||
|
|
s.writePacket(&packet.MobEquipment{
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
NewItem: instanceFromItem(s.br, mainHand),
|
||
|
|
})
|
||
|
|
// Show the off-hand item.
|
||
|
|
s.writePacket(&packet.MobEquipment{
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
NewItem: instanceFromItem(s.br, offHand),
|
||
|
|
WindowID: protocol.WindowIDOffHand,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityArmour ...
|
||
|
|
func (s *Session) ViewEntityArmour(e world.Entity) {
|
||
|
|
runtimeID := s.entityRuntimeID(e)
|
||
|
|
if runtimeID == selfEntityRuntimeID || s.entityHidden(e) {
|
||
|
|
// Don't view the items of the entity if the entity is the Controllable entity of the session.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
armoured, ok := e.(interface {
|
||
|
|
Armour() *inventory.Armour
|
||
|
|
})
|
||
|
|
if !ok {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
inv := armoured.Armour()
|
||
|
|
|
||
|
|
// Show the entity's armour
|
||
|
|
s.writePacket(&packet.MobArmourEquipment{
|
||
|
|
EntityRuntimeID: runtimeID,
|
||
|
|
Helmet: instanceFromItem(s.br, inv.Helmet()),
|
||
|
|
Chestplate: instanceFromItem(s.br, inv.Chestplate()),
|
||
|
|
Leggings: instanceFromItem(s.br, inv.Leggings()),
|
||
|
|
Boots: instanceFromItem(s.br, inv.Boots()),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewItemCooldown ...
|
||
|
|
func (s *Session) ViewItemCooldown(item world.Item, duration time.Duration) {
|
||
|
|
name, _ := item.EncodeItem()
|
||
|
|
s.writePacket(&packet.ClientStartItemCooldown{
|
||
|
|
Category: strings.Split(name, ":")[1],
|
||
|
|
Duration: int32(duration.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewSleepingPlayers ...
|
||
|
|
func (s *Session) ViewSleepingPlayers(sleeping, max int) {
|
||
|
|
buf := bytes.NewBuffer(nil)
|
||
|
|
_ = nbt.NewEncoderWithEncoding(buf, nbt.NetworkLittleEndian).Encode(map[string]any{
|
||
|
|
"ableToSleep": int32(max),
|
||
|
|
"overworldPlayerCount": int32(max),
|
||
|
|
"sleepingPlayerCount": int32(sleeping),
|
||
|
|
})
|
||
|
|
|
||
|
|
eventData := buf.Bytes()
|
||
|
|
|
||
|
|
s.writePacket(&packet.LevelEventGeneric{
|
||
|
|
EventID: packet.LevelEventSleepingPlayers,
|
||
|
|
SerialisedEventData: eventData[2 : len(eventData)-1],
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewParticle ...
|
||
|
|
func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) {
|
||
|
|
switch pa := p.(type) {
|
||
|
|
case particle.DragonEggTeleport:
|
||
|
|
xSign, ySign, zSign := 0, 0, 0
|
||
|
|
if pa.Diff.X() < 0 {
|
||
|
|
xSign = 1 << 24
|
||
|
|
}
|
||
|
|
if pa.Diff.Y() < 0 {
|
||
|
|
ySign = 1 << 25
|
||
|
|
}
|
||
|
|
if pa.Diff.Z() < 0 {
|
||
|
|
zSign = 1 << 26
|
||
|
|
}
|
||
|
|
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesDragonEgg,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: int32((((((abs(pa.Diff.X()) << 16) | (abs(pa.Diff.Y()) << 8)) | abs(pa.Diff.Z())) | xSign) | ySign) | zSign),
|
||
|
|
})
|
||
|
|
case particle.Note:
|
||
|
|
s.writePacket(&packet.BlockEvent{
|
||
|
|
EventType: pa.Instrument.Int32(),
|
||
|
|
EventData: int32(pa.Pitch),
|
||
|
|
Position: protocol.BlockPos{int32(pos.X()), int32(pos.Y()), int32(pos.Z())},
|
||
|
|
})
|
||
|
|
case particle.HugeExplosion:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesExplosion,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.BoneMeal:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleCropGrowth,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: int32(boolByte(pa.Area)),
|
||
|
|
})
|
||
|
|
case particle.BlockForceField:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleDenyBlock,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.BlockBreak:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesDestroyBlock,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: int32(s.br.BlockRuntimeID(pa.Block)),
|
||
|
|
})
|
||
|
|
case particle.PunchBlock:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesCrackBlock,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: int32(s.br.BlockRuntimeID(pa.Block)) | (int32(pa.Face) << 24),
|
||
|
|
})
|
||
|
|
case particle.EndermanTeleport:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesTeleport,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Flame:
|
||
|
|
if pa.Colour != (color.RGBA{}) {
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 57,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: nbtconv.Int32FromRGBA(pa.Colour),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 8,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Evaporate:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesEvaporateWater,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.SnowballPoof:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 15,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.EggSmash:
|
||
|
|
rid, meta, _ := world.ItemRuntimeID(item.Egg{})
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 14,
|
||
|
|
EventData: (rid << 16) | int32(meta),
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Splash:
|
||
|
|
if (pa.Colour == color.RGBA{}) {
|
||
|
|
pa.Colour, _ = effect.ResultingColour(nil)
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticlesPotionSplash,
|
||
|
|
EventData: (int32(pa.Colour.A) << 24) | (int32(pa.Colour.R) << 16) | (int32(pa.Colour.G) << 8) | int32(pa.Colour.B),
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Effect:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 34,
|
||
|
|
EventData: (int32(pa.Colour.A) << 24) | (int32(pa.Colour.R) << 16) | (int32(pa.Colour.G) << 8) | int32(pa.Colour.B),
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.EntityFlame:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 19,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Dust:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 33,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EventData: nbtconv.Int32FromRGBA(pa.Colour),
|
||
|
|
})
|
||
|
|
case particle.WaterDrip:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 28,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.LavaDrip:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 29,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.Lava:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 10,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case particle.DustPlume:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventParticleLegacyEvent | 88,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// tierToSoundEvent converts an item.ArmourTier to a sound event associated with equipping it.
|
||
|
|
func tierToSoundEvent(tier item.ArmourTier) string {
|
||
|
|
switch tier.(type) {
|
||
|
|
case item.ArmourTierLeather:
|
||
|
|
return packet.SoundEventEquipLeather
|
||
|
|
case item.ArmourTierCopper:
|
||
|
|
return packet.SoundEventEquipCopper
|
||
|
|
case item.ArmourTierGold:
|
||
|
|
return packet.SoundEventEquipGold
|
||
|
|
case item.ArmourTierChain:
|
||
|
|
return packet.SoundEventEquipChain
|
||
|
|
case item.ArmourTierIron:
|
||
|
|
return packet.SoundEventEquipIron
|
||
|
|
case item.ArmourTierDiamond:
|
||
|
|
return packet.SoundEventEquipDiamond
|
||
|
|
case item.ArmourTierNetherite:
|
||
|
|
return packet.SoundEventEquipNetherite
|
||
|
|
}
|
||
|
|
return packet.SoundEventEquipGeneric
|
||
|
|
}
|
||
|
|
|
||
|
|
// playSound plays a world.Sound at a position, disabling relative volume if set to true.
|
||
|
|
func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) {
|
||
|
|
pk := &packet.LevelSoundEvent{
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
EntityType: ":",
|
||
|
|
ExtraData: -1,
|
||
|
|
DisableRelativeVolume: disableRelative,
|
||
|
|
}
|
||
|
|
switch so := t.(type) {
|
||
|
|
case sound.EquipItem:
|
||
|
|
switch i := so.Item.(type) {
|
||
|
|
case item.Helmet:
|
||
|
|
pk.SoundType = tierToSoundEvent(i.Tier)
|
||
|
|
case item.Chestplate:
|
||
|
|
pk.SoundType = tierToSoundEvent(i.Tier)
|
||
|
|
case item.Leggings:
|
||
|
|
pk.SoundType = tierToSoundEvent(i.Tier)
|
||
|
|
case item.Boots:
|
||
|
|
pk.SoundType = tierToSoundEvent(i.Tier)
|
||
|
|
case item.Elytra:
|
||
|
|
pk.SoundType = packet.SoundEventEquipElytra
|
||
|
|
default:
|
||
|
|
pk.SoundType = packet.SoundEventEquipGeneric
|
||
|
|
}
|
||
|
|
case sound.Note:
|
||
|
|
pk.SoundType = packet.SoundEventNote
|
||
|
|
pk.ExtraData = (so.Instrument.Int32() << 8) | int32(so.Pitch)
|
||
|
|
case sound.DoorCrash:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundZombieDoorCrash,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.Explosion:
|
||
|
|
pk.SoundType = packet.SoundEventExplode
|
||
|
|
case sound.Thunder:
|
||
|
|
pk.SoundType, pk.EntityType = packet.SoundEventThunder, "minecraft:lightning_bolt"
|
||
|
|
case sound.Click:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundClick,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.SignWaxed:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventWaxOn,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case sound.WaxedSignFailedInteraction:
|
||
|
|
pk.SoundType = packet.SoundEventWaxedSignInteractFail
|
||
|
|
case sound.WaxRemoved:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventWaxOff,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case sound.CopperScraped:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventScrape,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case sound.Pop:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundInfinityArrowPickup,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.Teleport:
|
||
|
|
pk.SoundType = packet.SoundEventTeleport
|
||
|
|
case sound.ItemAdd:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundAddItem,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.ItemFrameRemove:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundItemFrameRemoveItem,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.ItemFrameRotate:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundItemFrameRotateItem,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.GhastWarning:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundGhastWarning,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.GhastShoot:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundGhastFireball,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.TNT:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundFuse,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.FireworkLaunch:
|
||
|
|
pk.SoundType = packet.SoundEventLaunch
|
||
|
|
case sound.FireworkHugeBlast:
|
||
|
|
pk.SoundType = packet.SoundEventLargeBlast
|
||
|
|
case sound.FireworkBlast:
|
||
|
|
pk.SoundType = packet.SoundEventBlast
|
||
|
|
case sound.FireworkTwinkle:
|
||
|
|
pk.SoundType = packet.SoundEventTwinkle
|
||
|
|
case sound.FurnaceCrackle:
|
||
|
|
pk.SoundType = packet.SoundEventFurnaceUse
|
||
|
|
case sound.CampfireCrackle:
|
||
|
|
pk.SoundType = packet.SoundEventCampfireCrackle
|
||
|
|
case sound.BlastFurnaceCrackle:
|
||
|
|
pk.SoundType = packet.SoundEventBlastFurnaceUse
|
||
|
|
case sound.SmokerCrackle:
|
||
|
|
pk.SoundType = packet.SoundEventSmokerUse
|
||
|
|
case sound.PotionBrewed:
|
||
|
|
pk.SoundType = packet.SoundEventPotionBrewed
|
||
|
|
case sound.UseSpyglass:
|
||
|
|
pk.SoundType = packet.SoundEventUseSpyglass
|
||
|
|
case sound.StopUsingSpyglass:
|
||
|
|
pk.SoundType = packet.SoundEventStopUsingSpyglass
|
||
|
|
case sound.GoatHorn:
|
||
|
|
switch so.Horn {
|
||
|
|
case sound.Ponder():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall0
|
||
|
|
case sound.Sing():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall1
|
||
|
|
case sound.Seek():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall2
|
||
|
|
case sound.Feel():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall3
|
||
|
|
case sound.Admire():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall4
|
||
|
|
case sound.Call():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall5
|
||
|
|
case sound.Yearn():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall6
|
||
|
|
case sound.Dream():
|
||
|
|
pk.SoundType = packet.SoundEventGoatCall7
|
||
|
|
}
|
||
|
|
case sound.FireExtinguish:
|
||
|
|
pk.SoundType = packet.SoundEventExtinguishFire
|
||
|
|
case sound.Ignite:
|
||
|
|
pk.SoundType = packet.SoundEventIgnite
|
||
|
|
case sound.Burning:
|
||
|
|
pk.SoundType = packet.SoundEventPlayerHurtOnFire
|
||
|
|
case sound.Drowning:
|
||
|
|
pk.SoundType = packet.SoundEventPlayerHurtDrown
|
||
|
|
case sound.Fall:
|
||
|
|
pk.EntityType = "minecraft:player"
|
||
|
|
if so.Distance > 4 {
|
||
|
|
pk.SoundType = packet.SoundEventFallBig
|
||
|
|
break
|
||
|
|
}
|
||
|
|
pk.SoundType = packet.SoundEventFallSmall
|
||
|
|
case sound.Burp:
|
||
|
|
pk.SoundType = packet.SoundEventBurp
|
||
|
|
case sound.DoorOpen:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventDoorOpen, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.DoorClose:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventDoorClose, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.TrapdoorOpen:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventTrapdoorOpen, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.TrapdoorClose:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventTrapdoorClose, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.FenceGateOpen:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventFenceGateOpen, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.FenceGateClose:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventFenceGateClose, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.Deny:
|
||
|
|
pk.SoundType = packet.SoundEventDeny
|
||
|
|
case sound.BlockPlace:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventPlace, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.AnvilLand:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundAnvilLand,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.AnvilUse:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundAnvilUsed,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.AnvilBreak:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundAnvilBroken,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.ChestClose:
|
||
|
|
pk.SoundType = packet.SoundEventChestClosed
|
||
|
|
case sound.ChestOpen:
|
||
|
|
pk.SoundType = packet.SoundEventChestOpen
|
||
|
|
case sound.EnderChestClose:
|
||
|
|
pk.SoundType = packet.SoundEventEnderChestClosed
|
||
|
|
case sound.EnderChestOpen:
|
||
|
|
pk.SoundType = packet.SoundEventEnderChestOpen
|
||
|
|
case sound.BarrelClose:
|
||
|
|
pk.SoundType = packet.SoundEventBarrelClose
|
||
|
|
case sound.BarrelOpen:
|
||
|
|
pk.SoundType = packet.SoundEventBarrelOpen
|
||
|
|
case sound.BlockBreaking:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventHit, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.ItemBreak:
|
||
|
|
pk.SoundType = packet.SoundEventBreak
|
||
|
|
case sound.ItemUseOn:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventItemUseOn, int32(s.br.BlockRuntimeID(so.Block))
|
||
|
|
case sound.Fizz:
|
||
|
|
pk.SoundType = packet.SoundEventFizz
|
||
|
|
case sound.GlassBreak:
|
||
|
|
pk.SoundType = packet.SoundEventGlass
|
||
|
|
case sound.Attack:
|
||
|
|
pk.SoundType, pk.EntityType = packet.SoundEventAttackStrong, "minecraft:player"
|
||
|
|
if !so.Damage {
|
||
|
|
pk.SoundType = packet.SoundEventAttackNoDamage
|
||
|
|
}
|
||
|
|
case sound.BucketFill:
|
||
|
|
if _, water := so.Liquid.(block.Water); water {
|
||
|
|
pk.SoundType = packet.SoundEventBucketFillWater
|
||
|
|
break
|
||
|
|
}
|
||
|
|
pk.SoundType = packet.SoundEventBucketFillLava
|
||
|
|
case sound.BucketEmpty:
|
||
|
|
if _, water := so.Liquid.(block.Water); water {
|
||
|
|
pk.SoundType = packet.SoundEventBucketEmptyWater
|
||
|
|
break
|
||
|
|
}
|
||
|
|
pk.SoundType = packet.SoundEventBucketEmptyLava
|
||
|
|
case sound.BowShoot:
|
||
|
|
pk.SoundType = packet.SoundEventBow
|
||
|
|
case sound.CrossbowLoad:
|
||
|
|
switch so.Stage {
|
||
|
|
case sound.CrossbowLoadingStart:
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowLoadingStart
|
||
|
|
if so.QuickCharge {
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowQuickChargeStart
|
||
|
|
}
|
||
|
|
case sound.CrossbowLoadingMiddle:
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowLoadingMiddle
|
||
|
|
if so.QuickCharge {
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowQuickChargeMiddle
|
||
|
|
}
|
||
|
|
case sound.CrossbowLoadingEnd:
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowLoadingEnd
|
||
|
|
if so.QuickCharge {
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowQuickChargeEnd
|
||
|
|
}
|
||
|
|
default:
|
||
|
|
panic("invalid crossbow loading stage")
|
||
|
|
}
|
||
|
|
case sound.CrossbowShoot:
|
||
|
|
pk.SoundType = packet.SoundEventCrossbowShoot
|
||
|
|
case sound.ArrowHit:
|
||
|
|
pk.SoundType = packet.SoundEventBowHit
|
||
|
|
case sound.ItemThrow:
|
||
|
|
pk.SoundType, pk.EntityType = packet.SoundEventThrow, "minecraft:player"
|
||
|
|
case sound.LevelUp:
|
||
|
|
pk.SoundType, pk.ExtraData = packet.SoundEventLevelUp, 0x10000000
|
||
|
|
case sound.Experience:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundExperienceOrbPickup,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.MusicDiscPlay:
|
||
|
|
switch so.DiscType {
|
||
|
|
case sound.Disc13():
|
||
|
|
pk.SoundType = packet.SoundEventRecord13
|
||
|
|
case sound.DiscCat():
|
||
|
|
pk.SoundType = packet.SoundEventRecordCat
|
||
|
|
case sound.DiscBlocks():
|
||
|
|
pk.SoundType = packet.SoundEventRecordBlocks
|
||
|
|
case sound.DiscChirp():
|
||
|
|
pk.SoundType = packet.SoundEventRecordChirp
|
||
|
|
case sound.DiscFar():
|
||
|
|
pk.SoundType = packet.SoundEventRecordFar
|
||
|
|
case sound.DiscMall():
|
||
|
|
pk.SoundType = packet.SoundEventRecordMall
|
||
|
|
case sound.DiscMellohi():
|
||
|
|
pk.SoundType = packet.SoundEventRecordMellohi
|
||
|
|
case sound.DiscStal():
|
||
|
|
pk.SoundType = packet.SoundEventRecordStal
|
||
|
|
case sound.DiscStrad():
|
||
|
|
pk.SoundType = packet.SoundEventRecordStrad
|
||
|
|
case sound.DiscWard():
|
||
|
|
pk.SoundType = packet.SoundEventRecordWard
|
||
|
|
case sound.Disc11():
|
||
|
|
pk.SoundType = packet.SoundEventRecord11
|
||
|
|
case sound.DiscWait():
|
||
|
|
pk.SoundType = packet.SoundEventRecordWait
|
||
|
|
case sound.DiscOtherside():
|
||
|
|
pk.SoundType = packet.SoundEventRecordOtherside
|
||
|
|
case sound.DiscPigstep():
|
||
|
|
pk.SoundType = packet.SoundEventRecordPigstep
|
||
|
|
case sound.Disc5():
|
||
|
|
pk.SoundType = packet.SoundEventRecord5
|
||
|
|
case sound.DiscRelic():
|
||
|
|
pk.SoundType = packet.SoundEventRecordRelic
|
||
|
|
case sound.DiscCreator():
|
||
|
|
pk.SoundType = packet.SoundEventRecordCreator
|
||
|
|
case sound.DiscCreatorMusicBox():
|
||
|
|
pk.SoundType = packet.SoundEventRecordCreatorMusicBox
|
||
|
|
case sound.DiscPrecipice():
|
||
|
|
pk.SoundType = packet.SoundEventRecordPrecipice
|
||
|
|
case sound.DiscTears():
|
||
|
|
pk.SoundType = packet.SoundEventRecordTears
|
||
|
|
case sound.DiscLavaChicken():
|
||
|
|
pk.SoundType = packet.SoundEventRecordLavaChicken
|
||
|
|
default:
|
||
|
|
panic(fmt.Errorf("disc (%v) does not have sound", so.DiscType.String()))
|
||
|
|
}
|
||
|
|
case sound.MusicDiscEnd:
|
||
|
|
pk.SoundType = packet.SoundEventRecordNull
|
||
|
|
case sound.FireCharge:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundBlazeFireball,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.ComposterEmpty:
|
||
|
|
pk.SoundType = packet.SoundEventComposterEmpty
|
||
|
|
case sound.ComposterFill:
|
||
|
|
pk.SoundType = packet.SoundEventComposterFill
|
||
|
|
case sound.ComposterFillLayer:
|
||
|
|
pk.SoundType = packet.SoundEventComposterFillLayer
|
||
|
|
case sound.ComposterReady:
|
||
|
|
pk.SoundType = packet.SoundEventComposterReady
|
||
|
|
case sound.PowerOn:
|
||
|
|
pk.SoundType = packet.SoundEventPowerOn
|
||
|
|
case sound.PowerOff:
|
||
|
|
pk.SoundType = packet.SoundEventPowerOff
|
||
|
|
case sound.LecternBookPlace:
|
||
|
|
pk.SoundType = packet.SoundEventLecternBookPlace
|
||
|
|
case sound.Totem:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventSoundTotemUsed,
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
})
|
||
|
|
case sound.DecoratedPotInserted:
|
||
|
|
s.writePacket(&packet.PlaySound{
|
||
|
|
SoundName: "block.decorated_pot.insert",
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
Volume: 1,
|
||
|
|
Pitch: 0.7 + 0.5*float32(so.Progress),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
case sound.DecoratedPotInsertFailed:
|
||
|
|
pk.SoundType = packet.SoundEventDecoratedPotInsertFail
|
||
|
|
case sound.LightningExplode:
|
||
|
|
s.writePacket(&packet.PlaySound{
|
||
|
|
SoundName: "ambient.weather.lightning.impact",
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
Volume: 1,
|
||
|
|
Pitch: 0.7,
|
||
|
|
})
|
||
|
|
case sound.LightningThunder:
|
||
|
|
s.writePacket(&packet.PlaySound{
|
||
|
|
SoundName: "ambient.weather.thunder",
|
||
|
|
Position: vec64To32(pos),
|
||
|
|
Volume: 1,
|
||
|
|
Pitch: 1.0,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
s.writePacket(pk)
|
||
|
|
}
|
||
|
|
|
||
|
|
// PlaySound plays a world.Sound to the client. The volume is not dependent on the distance to the source if it is a
|
||
|
|
// sound of the LevelSoundEvent packet.
|
||
|
|
func (s *Session) PlaySound(t world.Sound, pos mgl64.Vec3) {
|
||
|
|
if s == Nop {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.playSound(pos, t, true)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewSound ...
|
||
|
|
func (s *Session) ViewSound(pos mgl64.Vec3, soundType world.Sound) {
|
||
|
|
s.playSound(pos, soundType, false)
|
||
|
|
}
|
||
|
|
|
||
|
|
// OpenSign ...
|
||
|
|
func (s *Session) OpenSign(pos cube.Pos, frontSide bool) {
|
||
|
|
blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
|
||
|
|
s.writePacket(&packet.OpenSign{
|
||
|
|
Position: blockPos,
|
||
|
|
FrontSide: frontSide,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewFurnaceUpdate updates a furnace for the associated session based on previous times.
|
||
|
|
func (s *Session) ViewFurnaceUpdate(prevCookTime, cookTime, prevRemainingFuelTime, remainingFuelTime, prevMaxFuelTime, maxFuelTime time.Duration) {
|
||
|
|
if prevCookTime != cookTime {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataFurnaceTickCount,
|
||
|
|
Value: int32(cookTime.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if prevRemainingFuelTime != remainingFuelTime {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataFurnaceLitTime,
|
||
|
|
Value: int32(remainingFuelTime.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if prevMaxFuelTime != maxFuelTime {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataFurnaceLitDuration,
|
||
|
|
Value: int32(maxFuelTime.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewBrewingUpdate updates a brewing stand for the associated session based on previous times.
|
||
|
|
func (s *Session) ViewBrewingUpdate(prevBrewTime, brewTime time.Duration, prevFuelAmount, fuelAmount, prevFuelTotal, fuelTotal int32) {
|
||
|
|
if prevBrewTime != brewTime {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataBrewingStandBrewTime,
|
||
|
|
Value: int32(brewTime.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if prevFuelAmount != fuelAmount {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataBrewingStandFuelAmount,
|
||
|
|
Value: fuelAmount,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
if prevFuelTotal != fuelTotal {
|
||
|
|
s.writePacket(&packet.ContainerSetData{
|
||
|
|
WindowID: byte(s.openedWindowID.Load()),
|
||
|
|
Key: packet.ContainerDataBrewingStandFuelTotal,
|
||
|
|
Value: fuelTotal,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewBlockUpdate ...
|
||
|
|
func (s *Session) ViewBlockUpdate(pos cube.Pos, b world.Block, layer int) {
|
||
|
|
blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
|
||
|
|
s.writePacket(&packet.UpdateBlock{
|
||
|
|
Position: blockPos,
|
||
|
|
NewBlockRuntimeID: s.br.BlockRuntimeID(b),
|
||
|
|
Flags: packet.BlockUpdateNetwork,
|
||
|
|
Layer: uint32(layer),
|
||
|
|
})
|
||
|
|
if v, ok := b.(world.NBTer); ok {
|
||
|
|
if nbtData := v.EncodeNBT(); nbtData != nil {
|
||
|
|
nbtData["x"], nbtData["y"], nbtData["z"] = int32(pos.X()), int32(pos.Y()), int32(pos.Z())
|
||
|
|
s.writePacket(&packet.BlockActorData{
|
||
|
|
Position: blockPos,
|
||
|
|
NBTData: nbtData,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityAction ...
|
||
|
|
func (s *Session) ViewEntityAction(e world.Entity, a world.EntityAction) {
|
||
|
|
switch act := a.(type) {
|
||
|
|
case entity.SwingArmAction:
|
||
|
|
if _, ok := e.(Controllable); ok {
|
||
|
|
if s.entityRuntimeID(e) == selfEntityRuntimeID && s.swingingArm.Load() {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.Animate{
|
||
|
|
ActionType: packet.AnimateActionSwingArm,
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
})
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventStartAttacking,
|
||
|
|
})
|
||
|
|
case entity.HurtAction:
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventHurt,
|
||
|
|
})
|
||
|
|
case entity.CriticalHitAction:
|
||
|
|
if act.Count <= 0 {
|
||
|
|
act.Count = 55
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.Animate{
|
||
|
|
ActionType: packet.AnimateActionCriticalHit,
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
Data: float32(act.Count),
|
||
|
|
})
|
||
|
|
case entity.EnchantedHitAction:
|
||
|
|
if act.Count <= 0 {
|
||
|
|
act.Count = 15
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.Animate{
|
||
|
|
ActionType: packet.AnimateActionMagicCriticalHit,
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
Data: float32(act.Count),
|
||
|
|
})
|
||
|
|
case entity.DeathAction:
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventDeath,
|
||
|
|
})
|
||
|
|
case entity.PickedUpAction:
|
||
|
|
s.writePacket(&packet.TakeItemActor{
|
||
|
|
ItemEntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
TakerEntityRuntimeID: s.entityRuntimeID(act.Collector),
|
||
|
|
})
|
||
|
|
case entity.ArrowShakeAction:
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventShake,
|
||
|
|
EventData: int32(act.Duration.Milliseconds() / 50),
|
||
|
|
})
|
||
|
|
case entity.FireworkExplosionAction:
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventFireworksExplode,
|
||
|
|
})
|
||
|
|
case entity.EatAction:
|
||
|
|
if user, ok := e.(item.User); ok {
|
||
|
|
held, _ := user.HeldItems()
|
||
|
|
it := held.Item()
|
||
|
|
if held.Empty() {
|
||
|
|
// This can happen sometimes if the user switches between items very quickly, so just ignore the action.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if _, ok := it.(item.Consumable); !ok {
|
||
|
|
// Not consumable, refer to the comment above.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
rid, meta, _ := world.ItemRuntimeID(it)
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventFeed,
|
||
|
|
// It's a little weird how the runtime ID is still shifted 16 bits to the left here, given the
|
||
|
|
// runtime ID already includes the meta, but it seems to work.
|
||
|
|
EventData: (rid << 16) | int32(meta),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
case entity.TotemUseAction:
|
||
|
|
s.writePacket(&packet.ActorEvent{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EventType: packet.ActorEventTalismanActivate,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityState ...
|
||
|
|
func (s *Session) ViewEntityState(e world.Entity) {
|
||
|
|
s.writePacket(&packet.SetActorData{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
EntityMetadata: s.entityMetadata(e),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// entityMetadata returns the metadata of an entity as viewed by the session, including any overrides
|
||
|
|
// applied through its ViewLayer.
|
||
|
|
func (s *Session) entityMetadata(e world.Entity) protocol.EntityMetadata {
|
||
|
|
metadata := s.parseEntityMetadata(e)
|
||
|
|
if s.viewLayer == nil {
|
||
|
|
return metadata
|
||
|
|
}
|
||
|
|
if nt, ok := s.viewLayer.NameTag(e); ok {
|
||
|
|
metadata[protocol.EntityDataKeyName] = nt
|
||
|
|
if nt != "" {
|
||
|
|
metadata[protocol.EntityDataKeyAlwaysShowNameTag] = uint8(1)
|
||
|
|
if !metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName) {
|
||
|
|
metadata.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
|
||
|
|
}
|
||
|
|
if !metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagShowName) {
|
||
|
|
metadata.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagShowName)
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
metadata[protocol.EntityDataKeyAlwaysShowNameTag] = uint8(0)
|
||
|
|
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName) {
|
||
|
|
metadata.UnsetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagAlwaysShowName)
|
||
|
|
}
|
||
|
|
if metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagShowName) {
|
||
|
|
metadata.UnsetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagShowName)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if st, ok := s.viewLayer.ScoreTag(e); ok {
|
||
|
|
metadata[protocol.EntityDataKeyScore] = st
|
||
|
|
}
|
||
|
|
if visibility := s.viewLayer.Visibility(e); visibility.EnforceVisibility() {
|
||
|
|
invisibleFlag := metadata.Flag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
|
||
|
|
shouldForceVisible := visibility == world.EnforceVisible() && invisibleFlag
|
||
|
|
shouldForceInvisible := visibility == world.EnforceInvisible() && !invisibleFlag
|
||
|
|
if shouldForceVisible {
|
||
|
|
metadata.UnsetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
|
||
|
|
} else if shouldForceInvisible {
|
||
|
|
metadata.SetFlag(protocol.EntityDataKeyFlags, protocol.EntityDataFlagInvisible)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return metadata
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityAnimation ...
|
||
|
|
func (s *Session) ViewEntityAnimation(e world.Entity, a world.EntityAnimation) {
|
||
|
|
s.writePacket(&packet.AnimateEntity{
|
||
|
|
Animation: a.Name(),
|
||
|
|
NextState: a.NextState(),
|
||
|
|
StopCondition: a.StopCondition(),
|
||
|
|
Controller: a.Controller(),
|
||
|
|
EntityRuntimeIDs: []uint64{
|
||
|
|
s.entityRuntimeID(e),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// OpenBlockContainer ...
|
||
|
|
func (s *Session) OpenBlockContainer(pos cube.Pos, tx *world.Tx) {
|
||
|
|
if s.containerOpened.Load() && *s.openedPos.Load() == pos {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.closeCurrentContainer(tx, false)
|
||
|
|
|
||
|
|
b := tx.Block(pos)
|
||
|
|
if container, ok := b.(block.Container); ok {
|
||
|
|
s.openNormalContainer(container, pos, tx)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
// We hit a special kind of window like beacons, which are not actually opened server-side.
|
||
|
|
nextID := s.nextWindowID()
|
||
|
|
// The client does not send a ContainerClose packet for lecterns.
|
||
|
|
_, lectern := b.(block.Lectern)
|
||
|
|
if !lectern {
|
||
|
|
s.containerOpened.Store(true)
|
||
|
|
inv := inventory.New(1, nil)
|
||
|
|
s.openedWindow.Store(inv)
|
||
|
|
s.openedPos.Store(&pos)
|
||
|
|
}
|
||
|
|
|
||
|
|
var containerType byte
|
||
|
|
switch b := b.(type) {
|
||
|
|
case block.CraftingTable:
|
||
|
|
containerType = protocol.ContainerTypeWorkbench
|
||
|
|
case block.EnchantingTable:
|
||
|
|
containerType = protocol.ContainerTypeEnchantment
|
||
|
|
case block.Anvil:
|
||
|
|
containerType = protocol.ContainerTypeAnvil
|
||
|
|
case block.Beacon:
|
||
|
|
containerType = protocol.ContainerTypeBeacon
|
||
|
|
case block.Lectern:
|
||
|
|
containerType = protocol.ContainerTypeLectern
|
||
|
|
case block.Loom:
|
||
|
|
containerType = protocol.ContainerTypeLoom
|
||
|
|
case block.Grindstone:
|
||
|
|
containerType = protocol.ContainerTypeGrindstone
|
||
|
|
case block.Stonecutter:
|
||
|
|
containerType = protocol.ContainerTypeStonecutter
|
||
|
|
case block.SmithingTable:
|
||
|
|
containerType = protocol.ContainerTypeSmithingTable
|
||
|
|
case block.EnderChest:
|
||
|
|
b.AddViewer(tx, pos)
|
||
|
|
|
||
|
|
s.openedWindow.Store(s.enderChest)
|
||
|
|
defer s.sendInv(s.enderChest, uint32(nextID))
|
||
|
|
}
|
||
|
|
|
||
|
|
s.openedContainerID.Store(uint32(containerType))
|
||
|
|
s.writePacket(&packet.ContainerOpen{
|
||
|
|
WindowID: nextID,
|
||
|
|
ContainerType: containerType,
|
||
|
|
ContainerPosition: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
|
||
|
|
ContainerEntityUniqueID: -1,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// openNormalContainer opens a normal container that can hold items in it server-side.
|
||
|
|
func (s *Session) openNormalContainer(b block.Container, pos cube.Pos, tx *world.Tx) {
|
||
|
|
b.AddViewer(s, tx, pos) // Paired chests might update the block here.
|
||
|
|
b = tx.Block(pos).(block.Container)
|
||
|
|
|
||
|
|
nextID := s.nextWindowID()
|
||
|
|
s.containerOpened.Store(true)
|
||
|
|
inv := b.Inventory(tx, pos)
|
||
|
|
s.openedWindow.Store(inv)
|
||
|
|
s.openedPos.Store(&pos)
|
||
|
|
|
||
|
|
var containerType byte
|
||
|
|
switch b.(type) {
|
||
|
|
case block.Furnace:
|
||
|
|
containerType = protocol.ContainerTypeFurnace
|
||
|
|
case block.BrewingStand:
|
||
|
|
containerType = protocol.ContainerTypeBrewingStand
|
||
|
|
case block.BlastFurnace:
|
||
|
|
containerType = protocol.ContainerTypeBlastFurnace
|
||
|
|
case block.Smoker:
|
||
|
|
containerType = protocol.ContainerTypeSmoker
|
||
|
|
case block.Hopper:
|
||
|
|
containerType = protocol.ContainerTypeHopper
|
||
|
|
}
|
||
|
|
|
||
|
|
s.openedContainerID.Store(uint32(containerType))
|
||
|
|
s.writePacket(&packet.ContainerOpen{
|
||
|
|
WindowID: nextID,
|
||
|
|
ContainerType: containerType,
|
||
|
|
ContainerPosition: protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
|
||
|
|
ContainerEntityUniqueID: -1,
|
||
|
|
})
|
||
|
|
s.sendInv(b.Inventory(tx, pos), uint32(nextID))
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewSlotChange ...
|
||
|
|
func (s *Session) ViewSlotChange(slot int, newItem item.Stack) {
|
||
|
|
if !s.containerOpened.Load() {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if s.inTransaction.Load() {
|
||
|
|
// Don't send slot changes to the player itself.
|
||
|
|
return
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.InventorySlot{
|
||
|
|
WindowID: s.openedWindowID.Load(),
|
||
|
|
Slot: uint32(slot),
|
||
|
|
NewItem: instanceFromItem(s.br, newItem),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewBlockAction ...
|
||
|
|
func (s *Session) ViewBlockAction(pos cube.Pos, a world.BlockAction) {
|
||
|
|
blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
|
||
|
|
switch t := a.(type) {
|
||
|
|
case block.OpenAction:
|
||
|
|
s.writePacket(&packet.BlockEvent{
|
||
|
|
Position: blockPos,
|
||
|
|
EventType: packet.BlockEventChangeChestState,
|
||
|
|
EventData: 1,
|
||
|
|
})
|
||
|
|
case block.CloseAction:
|
||
|
|
s.writePacket(&packet.BlockEvent{
|
||
|
|
Position: blockPos,
|
||
|
|
EventType: packet.BlockEventChangeChestState,
|
||
|
|
})
|
||
|
|
case block.StartCrackAction:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventStartBlockCracking,
|
||
|
|
Position: vec64To32(pos.Vec3()),
|
||
|
|
EventData: int32(65535 / (t.BreakTime.Seconds() * 20)),
|
||
|
|
})
|
||
|
|
case block.StopCrackAction:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventStopBlockCracking,
|
||
|
|
Position: vec64To32(pos.Vec3()),
|
||
|
|
EventData: 0,
|
||
|
|
})
|
||
|
|
case block.ContinueCrackAction:
|
||
|
|
s.writePacket(&packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventUpdateBlockCracking,
|
||
|
|
Position: vec64To32(pos.Vec3()),
|
||
|
|
EventData: int32(65535 / (t.BreakTime.Seconds() * 20)),
|
||
|
|
})
|
||
|
|
case block.DecoratedPotWobbleAction:
|
||
|
|
nbt := t.DecoratedPot.EncodeNBT()
|
||
|
|
nbt["x"], nbt["y"], nbt["z"] = blockPos.X(), blockPos.Y(), blockPos.Z()
|
||
|
|
nbt["animation"] = boolByte(t.Success) + 1
|
||
|
|
s.writePacket(&packet.BlockActorData{
|
||
|
|
Position: blockPos,
|
||
|
|
NBTData: nbt,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEmote ...
|
||
|
|
func (s *Session) ViewEmote(player world.Entity, emote uuid.UUID) {
|
||
|
|
flags := byte(packet.EmoteFlagServerSide)
|
||
|
|
if s.emoteChatMuted {
|
||
|
|
flags |= packet.EmoteFlagMuteChat
|
||
|
|
}
|
||
|
|
s.writePacket(&packet.Emote{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(player),
|
||
|
|
EmoteID: emote.String(),
|
||
|
|
Flags: flags,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewSkin ...
|
||
|
|
func (s *Session) ViewSkin(e world.Entity) {
|
||
|
|
if v, ok := e.(Controllable); ok {
|
||
|
|
s.writePacket(&packet.PlayerSkin{
|
||
|
|
UUID: v.UUID(),
|
||
|
|
Skin: skinToProtocol(v.Skin()),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewWorldSpawn ...
|
||
|
|
func (s *Session) ViewWorldSpawn(pos cube.Pos) {
|
||
|
|
blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
|
||
|
|
s.writePacket(&packet.SetSpawnPosition{
|
||
|
|
SpawnType: packet.SpawnTypeWorld,
|
||
|
|
Position: blockPos,
|
||
|
|
Dimension: packet.DimensionOverworld,
|
||
|
|
SpawnPosition: blockPos,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewWeather ...
|
||
|
|
func (s *Session) ViewWeather(raining, thunder bool) {
|
||
|
|
pk := &packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventStopRaining,
|
||
|
|
}
|
||
|
|
if raining {
|
||
|
|
pk.EventType, pk.EventData = packet.LevelEventStartRaining, int32(rand.IntN(50000)+10000)
|
||
|
|
}
|
||
|
|
s.writePacket(pk)
|
||
|
|
|
||
|
|
pk = &packet.LevelEvent{
|
||
|
|
EventType: packet.LevelEventStopThunderstorm,
|
||
|
|
}
|
||
|
|
if thunder {
|
||
|
|
pk.EventType, pk.EventData = packet.LevelEventStartThunderstorm, int32(rand.IntN(50000)+10000)
|
||
|
|
}
|
||
|
|
s.writePacket(pk)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ViewEntityWake ...
|
||
|
|
func (s *Session) ViewEntityWake(e world.Entity) {
|
||
|
|
s.writePacket(&packet.Animate{
|
||
|
|
EntityRuntimeID: s.entityRuntimeID(e),
|
||
|
|
ActionType: packet.AnimateActionStopSleep,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// nextWindowID produces the next window ID for a new window. It is an int of 1-99.
|
||
|
|
func (s *Session) nextWindowID() byte {
|
||
|
|
if s.openedWindowID.CompareAndSwap(99, 1) {
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
return byte(s.openedWindowID.Add(1))
|
||
|
|
}
|
||
|
|
|
||
|
|
// closeWindow closes the container window currently opened. If no window is open, closeWindow will do
|
||
|
|
// nothing.
|
||
|
|
func (s *Session) closeWindow(clientRequested bool) bool {
|
||
|
|
if !s.containerOpened.CompareAndSwap(true, false) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
containerType := byte(s.openedContainerID.Load())
|
||
|
|
windowID := byte(s.openedWindowID.Load())
|
||
|
|
|
||
|
|
s.openedContainerID.Store(0)
|
||
|
|
s.openedWindow.Store(inventory.New(1, nil))
|
||
|
|
if !clientRequested {
|
||
|
|
s.writePacket(&packet.ContainerClose{
|
||
|
|
WindowID: windowID,
|
||
|
|
ContainerType: containerType,
|
||
|
|
ServerSide: true,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
// entityRuntimeID returns the runtime ID of the entity passed.
|
||
|
|
// noinspection GoCommentLeadingSpace
|
||
|
|
func (s *Session) entityRuntimeID(e world.Entity) uint64 {
|
||
|
|
return s.handleRuntimeID(e.H())
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Session) handleRuntimeID(e *world.EntityHandle) uint64 {
|
||
|
|
s.entityMutex.RLock()
|
||
|
|
defer s.entityMutex.RUnlock()
|
||
|
|
|
||
|
|
if id, ok := s.entityRuntimeIDs[e]; ok {
|
||
|
|
return id
|
||
|
|
}
|
||
|
|
s.conf.Log.Debug("entity runtime ID not found", "UUID", e.UUID().String())
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// entityFromRuntimeID attempts to return an entity by its runtime ID. False is returned if no entity with the
|
||
|
|
// ID could be found.
|
||
|
|
func (s *Session) entityFromRuntimeID(id uint64) (*world.EntityHandle, bool) {
|
||
|
|
s.entityMutex.RLock()
|
||
|
|
e, ok := s.entities[id]
|
||
|
|
s.entityMutex.RUnlock()
|
||
|
|
return e, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// vec32To64 converts a mgl32.Vec3 to a mgl64.Vec3.
|
||
|
|
func vec32To64(vec3 mgl32.Vec3) mgl64.Vec3 {
|
||
|
|
return mgl64.Vec3{float64(vec3[0]), float64(vec3[1]), float64(vec3[2])}
|
||
|
|
}
|
||
|
|
|
||
|
|
// vec64To32 converts a mgl64.Vec3 to a mgl32.Vec3.
|
||
|
|
func vec64To32(vec3 mgl64.Vec3) mgl32.Vec3 {
|
||
|
|
return mgl32.Vec3{float32(vec3[0]), float32(vec3[1]), float32(vec3[2])}
|
||
|
|
}
|
||
|
|
|
||
|
|
// vec2To32 converts a mgl64.Vec2 to a mgl32.Vec2.
|
||
|
|
func vec2To32(vec2 mgl64.Vec2) mgl32.Vec2 {
|
||
|
|
return mgl32.Vec2{float32(vec2[0]), float32(vec2[1])}
|
||
|
|
}
|
||
|
|
|
||
|
|
// blockPosFromProtocol ...
|
||
|
|
func blockPosFromProtocol(pos protocol.BlockPos) cube.Pos {
|
||
|
|
return cube.Pos{int(pos.X()), int(pos.Y()), int(pos.Z())}
|
||
|
|
}
|
||
|
|
|
||
|
|
// boolByte returns 1 if the bool passed is true, or 0 if it is false.
|
||
|
|
func boolByte(b bool) uint8 {
|
||
|
|
if b {
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// abs ...
|
||
|
|
func abs(a int) int {
|
||
|
|
if a < 0 {
|
||
|
|
return -a
|
||
|
|
}
|
||
|
|
return a
|
||
|
|
}
|