Files
mc/server/session/world.go
T
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

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
}