github.com/df-mc/dragonfly@v0.9.13/server/session/world.go (about)

     1  package session
     2  
     3  import (
     4  	"github.com/df-mc/dragonfly/server/entity/effect"
     5  	"image/color"
     6  	"math/rand"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/df-mc/dragonfly/server/block"
    11  	"github.com/df-mc/dragonfly/server/block/cube"
    12  	"github.com/df-mc/dragonfly/server/entity"
    13  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
    14  	"github.com/df-mc/dragonfly/server/item"
    15  	"github.com/df-mc/dragonfly/server/item/inventory"
    16  	"github.com/df-mc/dragonfly/server/world"
    17  	"github.com/df-mc/dragonfly/server/world/particle"
    18  	"github.com/df-mc/dragonfly/server/world/sound"
    19  	"github.com/go-gl/mathgl/mgl32"
    20  	"github.com/go-gl/mathgl/mgl64"
    21  	"github.com/google/uuid"
    22  	"github.com/sandertv/gophertunnel/minecraft/protocol"
    23  	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
    24  )
    25  
    26  // NetworkEncodeableEntity is a world.EntityType where the save ID and network
    27  // ID are not the same.
    28  type NetworkEncodeableEntity interface {
    29  	// NetworkEncodeEntity returns the network type ID of the entity. This is
    30  	// NOT the save ID.
    31  	NetworkEncodeEntity() string
    32  }
    33  
    34  // OffsetEntity is a world.EntityType that has an additional offset when sent
    35  // over network. This is mostly the case for older entities such as players and
    36  // TNT.
    37  type OffsetEntity interface {
    38  	NetworkOffset() float64
    39  }
    40  
    41  // entityHidden checks if a world.Entity is being explicitly hidden from the Session.
    42  func (s *Session) entityHidden(e world.Entity) bool {
    43  	s.entityMutex.RLock()
    44  	_, ok := s.hiddenEntities[e]
    45  	s.entityMutex.RUnlock()
    46  	return ok
    47  }
    48  
    49  // ViewEntity ...
    50  func (s *Session) ViewEntity(e world.Entity) {
    51  	if s.entityRuntimeID(e) == selfEntityRuntimeID {
    52  		s.ViewEntityState(e)
    53  		return
    54  	}
    55  	if s.entityHidden(e) {
    56  		return
    57  	}
    58  	var runtimeID uint64
    59  
    60  	_, controllable := e.(Controllable)
    61  
    62  	s.entityMutex.Lock()
    63  	if id, ok := s.entityRuntimeIDs[e]; ok && controllable {
    64  		runtimeID = id
    65  	} else {
    66  		s.currentEntityRuntimeID += 1
    67  		runtimeID = s.currentEntityRuntimeID
    68  		s.entityRuntimeIDs[e] = runtimeID
    69  		s.entities[runtimeID] = e
    70  	}
    71  	s.entityMutex.Unlock()
    72  
    73  	yaw, pitch := e.Rotation().Elem()
    74  	metadata := s.parseEntityMetadata(e)
    75  
    76  	id := e.Type().EncodeEntity()
    77  	switch v := e.(type) {
    78  	case Controllable:
    79  		actualPlayer := false
    80  
    81  		sessionMu.Lock()
    82  		for _, s := range sessions {
    83  			if s.c.UUID() == v.UUID() {
    84  				actualPlayer = true
    85  				break
    86  			}
    87  		}
    88  		sessionMu.Unlock()
    89  		if !actualPlayer {
    90  			s.writePacket(&packet.PlayerList{ActionType: packet.PlayerListActionAdd, Entries: []protocol.PlayerListEntry{{
    91  				UUID:           v.UUID(),
    92  				EntityUniqueID: int64(runtimeID),
    93  				Username:       v.Name(),
    94  				Skin:           skinToProtocol(v.Skin()),
    95  			}}})
    96  		}
    97  
    98  		s.writePacket(&packet.AddPlayer{
    99  			EntityMetadata:  metadata,
   100  			EntityRuntimeID: runtimeID,
   101  			GameType:        gameTypeFromMode(v.GameMode()),
   102  			HeadYaw:         float32(yaw),
   103  			Pitch:           float32(pitch),
   104  			Position:        vec64To32(e.Position()),
   105  			UUID:            v.UUID(),
   106  			Username:        v.Name(),
   107  			Yaw:             float32(yaw),
   108  			AbilityData: protocol.AbilityData{
   109  				EntityUniqueID: int64(runtimeID),
   110  				Layers: []protocol.AbilityLayer{{
   111  					Type:      protocol.AbilityLayerTypeBase,
   112  					Abilities: protocol.AbilityCount - 1,
   113  				}},
   114  			},
   115  		})
   116  		if !actualPlayer {
   117  			s.writePacket(&packet.PlayerList{ActionType: packet.PlayerListActionRemove, Entries: []protocol.PlayerListEntry{{
   118  				UUID: v.UUID(),
   119  			}}})
   120  		}
   121  		return
   122  	case *entity.Ent:
   123  		switch e.Type().(type) {
   124  		case entity.ItemType:
   125  			s.writePacket(&packet.AddItemActor{
   126  				EntityUniqueID:  int64(runtimeID),
   127  				EntityRuntimeID: runtimeID,
   128  				Item:            instanceFromItem(v.Behaviour().(*entity.ItemBehaviour).Item()),
   129  				Position:        vec64To32(v.Position()),
   130  				Velocity:        vec64To32(v.Velocity()),
   131  				EntityMetadata:  metadata,
   132  			})
   133  			return
   134  		case entity.TextType:
   135  			metadata[protocol.EntityDataKeyVariant] = int32(world.BlockRuntimeID(block.Air{}))
   136  		case entity.FallingBlockType:
   137  			metadata[protocol.EntityDataKeyVariant] = int32(world.BlockRuntimeID(v.Behaviour().(*entity.FallingBlockBehaviour).Block()))
   138  		}
   139  	}
   140  	if v, ok := e.Type().(NetworkEncodeableEntity); ok {
   141  		id = v.NetworkEncodeEntity()
   142  	}
   143  
   144  	var vel mgl64.Vec3
   145  	if v, ok := e.(interface{ Velocity() mgl64.Vec3 }); ok {
   146  		vel = v.Velocity()
   147  	}
   148  
   149  	s.writePacket(&packet.AddActor{
   150  		EntityUniqueID:  int64(runtimeID),
   151  		EntityRuntimeID: runtimeID,
   152  		EntityType:      id,
   153  		EntityMetadata:  metadata,
   154  		Position:        vec64To32(e.Position()),
   155  		Velocity:        vec64To32(vel),
   156  		Pitch:           float32(pitch),
   157  		Yaw:             float32(yaw),
   158  		HeadYaw:         float32(yaw),
   159  	})
   160  }
   161  
   162  // ViewEntityGameMode ...
   163  func (s *Session) ViewEntityGameMode(e world.Entity) {
   164  	if s.entityHidden(e) {
   165  		return
   166  	}
   167  	c, ok := e.(Controllable)
   168  	if !ok {
   169  		return
   170  	}
   171  	s.writePacket(&packet.UpdatePlayerGameType{
   172  		GameType:       gameTypeFromMode(c.GameMode()),
   173  		PlayerUniqueID: int64(s.entityRuntimeID(c)),
   174  	})
   175  }
   176  
   177  // HideEntity ...
   178  func (s *Session) HideEntity(e world.Entity) {
   179  	if s.entityRuntimeID(e) == selfEntityRuntimeID {
   180  		return
   181  	}
   182  
   183  	s.entityMutex.Lock()
   184  	id, ok := s.entityRuntimeIDs[e]
   185  	if _, controllable := e.(Controllable); !controllable {
   186  		delete(s.entityRuntimeIDs, e)
   187  		delete(s.entities, id)
   188  	}
   189  	s.entityMutex.Unlock()
   190  	if !ok {
   191  		// The entity was already removed some other way. We don't need to send a packet.
   192  		return
   193  	}
   194  	s.writePacket(&packet.RemoveActor{EntityUniqueID: int64(id)})
   195  }
   196  
   197  // ViewEntityMovement ...
   198  func (s *Session) ViewEntityMovement(e world.Entity, pos mgl64.Vec3, rot cube.Rotation, onGround bool) {
   199  	id := s.entityRuntimeID(e)
   200  	if id == selfEntityRuntimeID || s.entityHidden(e) {
   201  		return
   202  	}
   203  
   204  	flags := byte(0)
   205  	if onGround {
   206  		flags |= packet.MoveFlagOnGround
   207  	}
   208  	s.writePacket(&packet.MoveActorAbsolute{
   209  		EntityRuntimeID: id,
   210  		Position:        vec64To32(pos.Add(entityOffset(e))),
   211  		Rotation:        vec64To32(mgl64.Vec3{rot.Pitch(), rot.Yaw(), rot.Yaw()}),
   212  		Flags:           flags,
   213  	})
   214  }
   215  
   216  // ViewEntityVelocity ...
   217  func (s *Session) ViewEntityVelocity(e world.Entity, velocity mgl64.Vec3) {
   218  	if s.entityHidden(e) {
   219  		return
   220  	}
   221  	s.writePacket(&packet.SetActorMotion{
   222  		EntityRuntimeID: s.entityRuntimeID(e),
   223  		Velocity:        vec64To32(velocity),
   224  	})
   225  }
   226  
   227  // entityOffset returns the offset that entities have client-side.
   228  func entityOffset(e world.Entity) mgl64.Vec3 {
   229  	if offset, ok := e.Type().(OffsetEntity); ok {
   230  		return mgl64.Vec3{0, offset.NetworkOffset()}
   231  	}
   232  	return mgl64.Vec3{}
   233  }
   234  
   235  // ViewTime ...
   236  func (s *Session) ViewTime(time int) {
   237  	s.writePacket(&packet.SetTime{Time: int32(time)})
   238  }
   239  
   240  // ViewEntityTeleport ...
   241  func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) {
   242  	id := s.entityRuntimeID(e)
   243  	if s.entityHidden(e) {
   244  		return
   245  	}
   246  
   247  	yaw, pitch := e.Rotation().Elem()
   248  	if id == selfEntityRuntimeID {
   249  		s.chunkLoader.Move(position)
   250  		s.teleportPos.Store(&position)
   251  	}
   252  
   253  	s.writePacket(&packet.SetActorMotion{EntityRuntimeID: id})
   254  	if _, ok := e.(Controllable); ok {
   255  		s.writePacket(&packet.MovePlayer{
   256  			EntityRuntimeID: id,
   257  			Position:        vec64To32(position.Add(entityOffset(e))),
   258  			Pitch:           float32(pitch),
   259  			Yaw:             float32(yaw),
   260  			HeadYaw:         float32(yaw),
   261  			Mode:            packet.MoveModeTeleport,
   262  		})
   263  		return
   264  	}
   265  	s.writePacket(&packet.MoveActorAbsolute{
   266  		EntityRuntimeID: id,
   267  		Position:        vec64To32(position.Add(entityOffset(e))),
   268  		Rotation:        vec64To32(mgl64.Vec3{pitch, yaw, yaw}),
   269  		Flags:           packet.MoveFlagTeleport,
   270  	})
   271  }
   272  
   273  // ViewEntityItems ...
   274  func (s *Session) ViewEntityItems(e world.Entity) {
   275  	runtimeID := s.entityRuntimeID(e)
   276  	if runtimeID == selfEntityRuntimeID || s.entityHidden(e) {
   277  		// Don't view the items of the entity if the entity is the Controllable entity of the session.
   278  		return
   279  	}
   280  	c, ok := e.(item.Carrier)
   281  	if !ok {
   282  		return
   283  	}
   284  
   285  	mainHand, offHand := c.HeldItems()
   286  
   287  	// Show the main hand item.
   288  	s.writePacket(&packet.MobEquipment{
   289  		EntityRuntimeID: runtimeID,
   290  		NewItem:         instanceFromItem(mainHand),
   291  	})
   292  	// Show the off-hand item.
   293  	s.writePacket(&packet.MobEquipment{
   294  		EntityRuntimeID: runtimeID,
   295  		NewItem:         instanceFromItem(offHand),
   296  		WindowID:        protocol.WindowIDOffHand,
   297  	})
   298  }
   299  
   300  // ViewEntityArmour ...
   301  func (s *Session) ViewEntityArmour(e world.Entity) {
   302  	runtimeID := s.entityRuntimeID(e)
   303  	if runtimeID == selfEntityRuntimeID || s.entityHidden(e) {
   304  		// Don't view the items of the entity if the entity is the Controllable entity of the session.
   305  		return
   306  	}
   307  	armoured, ok := e.(interface {
   308  		Armour() *inventory.Armour
   309  	})
   310  	if !ok {
   311  		return
   312  	}
   313  
   314  	inv := armoured.Armour()
   315  
   316  	// Show the main hand item.
   317  	s.writePacket(&packet.MobArmourEquipment{
   318  		EntityRuntimeID: runtimeID,
   319  		Helmet:          instanceFromItem(inv.Helmet()),
   320  		Chestplate:      instanceFromItem(inv.Chestplate()),
   321  		Leggings:        instanceFromItem(inv.Leggings()),
   322  		Boots:           instanceFromItem(inv.Boots()),
   323  	})
   324  }
   325  
   326  // ViewItemCooldown ...
   327  func (s *Session) ViewItemCooldown(item world.Item, duration time.Duration) {
   328  	name, _ := item.EncodeItem()
   329  	s.writePacket(&packet.ClientStartItemCooldown{
   330  		Category: strings.Split(name, ":")[1],
   331  		Duration: int32(duration.Milliseconds() / 50),
   332  	})
   333  }
   334  
   335  // ViewParticle ...
   336  func (s *Session) ViewParticle(pos mgl64.Vec3, p world.Particle) {
   337  	switch pa := p.(type) {
   338  	case particle.DragonEggTeleport:
   339  		xSign, ySign, zSign := 0, 0, 0
   340  		if pa.Diff.X() < 0 {
   341  			xSign = 1 << 24
   342  		}
   343  		if pa.Diff.Y() < 0 {
   344  			ySign = 1 << 25
   345  		}
   346  		if pa.Diff.Z() < 0 {
   347  			zSign = 1 << 26
   348  		}
   349  
   350  		s.writePacket(&packet.LevelEvent{
   351  			EventType: packet.LevelEventParticlesDragonEgg,
   352  			Position:  vec64To32(pos),
   353  			EventData: int32((((((abs(pa.Diff.X()) << 16) | (abs(pa.Diff.Y()) << 8)) | abs(pa.Diff.Z())) | xSign) | ySign) | zSign),
   354  		})
   355  	case particle.Note:
   356  		s.writePacket(&packet.BlockEvent{
   357  			EventType: pa.Instrument.Int32(),
   358  			EventData: int32(pa.Pitch),
   359  			Position:  protocol.BlockPos{int32(pos.X()), int32(pos.Y()), int32(pos.Z())},
   360  		})
   361  	case particle.HugeExplosion:
   362  		s.writePacket(&packet.LevelEvent{
   363  			EventType: packet.LevelEventParticlesExplosion,
   364  			Position:  vec64To32(pos),
   365  		})
   366  	case particle.BoneMeal:
   367  		s.writePacket(&packet.LevelEvent{
   368  			EventType: packet.LevelEventParticleCropGrowth,
   369  			Position:  vec64To32(pos),
   370  		})
   371  	case particle.BlockForceField:
   372  		s.writePacket(&packet.LevelEvent{
   373  			EventType: packet.LevelEventParticleDenyBlock,
   374  			Position:  vec64To32(pos),
   375  		})
   376  	case particle.BlockBreak:
   377  		s.writePacket(&packet.LevelEvent{
   378  			EventType: packet.LevelEventParticlesDestroyBlock,
   379  			Position:  vec64To32(pos),
   380  			EventData: int32(world.BlockRuntimeID(pa.Block)),
   381  		})
   382  	case particle.PunchBlock:
   383  		s.writePacket(&packet.LevelEvent{
   384  			EventType: packet.LevelEventParticlesCrackBlock,
   385  			Position:  vec64To32(pos),
   386  			EventData: int32(world.BlockRuntimeID(pa.Block)) | (int32(pa.Face) << 24),
   387  		})
   388  	case particle.EndermanTeleport:
   389  		s.writePacket(&packet.LevelEvent{
   390  			EventType: packet.LevelEventParticlesTeleport,
   391  			Position:  vec64To32(pos),
   392  		})
   393  	case particle.Flame:
   394  		if pa.Colour != (color.RGBA{}) {
   395  			s.writePacket(&packet.LevelEvent{
   396  				EventType: packet.LevelEventParticleLegacyEvent | 56,
   397  				Position:  vec64To32(pos),
   398  				EventData: nbtconv.Int32FromRGBA(pa.Colour),
   399  			})
   400  			return
   401  		}
   402  		s.writePacket(&packet.LevelEvent{
   403  			EventType: packet.LevelEventParticleLegacyEvent | 8,
   404  			Position:  vec64To32(pos),
   405  		})
   406  	case particle.Evaporate:
   407  		s.writePacket(&packet.LevelEvent{
   408  			EventType: packet.LevelEventParticlesEvaporateWater,
   409  			Position:  vec64To32(pos),
   410  		})
   411  	case particle.SnowballPoof:
   412  		s.writePacket(&packet.LevelEvent{
   413  			EventType: packet.LevelEventParticleLegacyEvent | 15,
   414  			Position:  vec64To32(pos),
   415  		})
   416  	case particle.EggSmash:
   417  		rid, meta, _ := world.ItemRuntimeID(item.Egg{})
   418  		s.writePacket(&packet.LevelEvent{
   419  			EventType: packet.LevelEventParticleLegacyEvent | 14,
   420  			EventData: (rid << 16) | int32(meta),
   421  			Position:  vec64To32(pos),
   422  		})
   423  	case particle.Splash:
   424  		if (pa.Colour == color.RGBA{}) {
   425  			pa.Colour, _ = effect.ResultingColour(nil)
   426  		}
   427  		s.writePacket(&packet.LevelEvent{
   428  			EventType: packet.LevelEventParticlesPotionSplash,
   429  			EventData: (int32(pa.Colour.A) << 24) | (int32(pa.Colour.R) << 16) | (int32(pa.Colour.G) << 8) | int32(pa.Colour.B),
   430  			Position:  vec64To32(pos),
   431  		})
   432  	case particle.Effect:
   433  		s.writePacket(&packet.LevelEvent{
   434  			EventType: packet.LevelEventParticleLegacyEvent | 33,
   435  			EventData: (int32(pa.Colour.A) << 24) | (int32(pa.Colour.R) << 16) | (int32(pa.Colour.G) << 8) | int32(pa.Colour.B),
   436  			Position:  vec64To32(pos),
   437  		})
   438  	case particle.EntityFlame:
   439  		s.writePacket(&packet.LevelEvent{
   440  			EventType: packet.LevelEventParticleLegacyEvent | 18,
   441  			Position:  vec64To32(pos),
   442  		})
   443  	case particle.Dust:
   444  		s.writePacket(&packet.LevelEvent{
   445  			EventType: packet.LevelEventParticleLegacyEvent | 32,
   446  			Position:  vec64To32(pos),
   447  			EventData: nbtconv.Int32FromRGBA(pa.Colour),
   448  		})
   449  	case particle.WaterDrip:
   450  		s.writePacket(&packet.LevelEvent{
   451  			EventType: packet.LevelEventParticleLegacyEvent | 27,
   452  			Position:  vec64To32(pos),
   453  		})
   454  	case particle.LavaDrip:
   455  		s.writePacket(&packet.LevelEvent{
   456  			EventType: packet.LevelEventParticleLegacyEvent | 28,
   457  			Position:  vec64To32(pos),
   458  		})
   459  	case particle.Lava:
   460  		s.writePacket(&packet.LevelEvent{
   461  			EventType: packet.LevelEventParticleLegacyEvent | 10,
   462  			Position:  vec64To32(pos),
   463  		})
   464  	}
   465  }
   466  
   467  // tierToSoundEvent converts an item.ArmourTier to a sound event associated with equipping it.
   468  func tierToSoundEvent(tier item.ArmourTier) uint32 {
   469  	switch tier.(type) {
   470  	case item.ArmourTierLeather:
   471  		return packet.SoundEventEquipLeather
   472  	case item.ArmourTierGold:
   473  		return packet.SoundEventEquipGold
   474  	case item.ArmourTierChain:
   475  		return packet.SoundEventEquipChain
   476  	case item.ArmourTierIron:
   477  		return packet.SoundEventEquipIron
   478  	case item.ArmourTierDiamond:
   479  		return packet.SoundEventEquipDiamond
   480  	case item.ArmourTierNetherite:
   481  		return packet.SoundEventEquipNetherite
   482  	}
   483  	return packet.SoundEventEquipGeneric
   484  }
   485  
   486  // playSound plays a world.Sound at a position, disabling relative volume if set to true.
   487  func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool) {
   488  	pk := &packet.LevelSoundEvent{
   489  		Position:              vec64To32(pos),
   490  		EntityType:            ":",
   491  		ExtraData:             -1,
   492  		DisableRelativeVolume: disableRelative,
   493  	}
   494  	switch so := t.(type) {
   495  	case sound.EquipItem:
   496  		switch i := so.Item.(type) {
   497  		case item.Helmet:
   498  			pk.SoundType = tierToSoundEvent(i.Tier)
   499  		case item.Chestplate:
   500  			pk.SoundType = tierToSoundEvent(i.Tier)
   501  		case item.Leggings:
   502  			pk.SoundType = tierToSoundEvent(i.Tier)
   503  		case item.Boots:
   504  			pk.SoundType = tierToSoundEvent(i.Tier)
   505  		case item.Elytra:
   506  			pk.SoundType = packet.SoundEventEquipElytra
   507  		default:
   508  			pk.SoundType = packet.SoundEventEquipGeneric
   509  		}
   510  	case sound.Note:
   511  		pk.SoundType = packet.SoundEventNote
   512  		pk.ExtraData = (so.Instrument.Int32() << 8) | int32(so.Pitch)
   513  	case sound.DoorCrash:
   514  		s.writePacket(&packet.LevelEvent{
   515  			EventType: packet.LevelEventSoundZombieDoorCrash,
   516  			Position:  vec64To32(pos),
   517  		})
   518  		return
   519  	case sound.Explosion:
   520  		pk.SoundType = packet.SoundEventExplode
   521  	case sound.Thunder:
   522  		pk.SoundType, pk.EntityType = packet.SoundEventThunder, "minecraft:lightning_bolt"
   523  	case sound.Click:
   524  		s.writePacket(&packet.LevelEvent{
   525  			EventType: packet.LevelEventSoundClick,
   526  			Position:  vec64To32(pos),
   527  		})
   528  		return
   529  	case sound.Pop:
   530  		s.writePacket(&packet.LevelEvent{
   531  			EventType: packet.LevelEventSoundInfinityArrowPickup,
   532  			Position:  vec64To32(pos),
   533  		})
   534  		return
   535  	case sound.Teleport:
   536  		pk.SoundType = packet.SoundEventTeleport
   537  	case sound.ItemFrameAdd:
   538  		s.writePacket(&packet.LevelEvent{
   539  			EventType: packet.LevelEventSoundAddItem,
   540  			Position:  vec64To32(pos),
   541  		})
   542  		return
   543  	case sound.ItemFrameRemove:
   544  		s.writePacket(&packet.LevelEvent{
   545  			EventType: packet.LevelEventSoundItemFrameRemoveItem,
   546  			Position:  vec64To32(pos),
   547  		})
   548  		return
   549  	case sound.ItemFrameRotate:
   550  		s.writePacket(&packet.LevelEvent{
   551  			EventType: packet.LevelEventSoundItemFrameRotateItem,
   552  			Position:  vec64To32(pos),
   553  		})
   554  		return
   555  	case sound.GhastWarning:
   556  		s.writePacket(&packet.LevelEvent{
   557  			EventType: packet.LevelEventSoundGhastWarning,
   558  			Position:  vec64To32(pos),
   559  		})
   560  		return
   561  	case sound.GhastShoot:
   562  		s.writePacket(&packet.LevelEvent{
   563  			EventType: packet.LevelEventSoundGhastFireball,
   564  			Position:  vec64To32(pos),
   565  		})
   566  		return
   567  	case sound.TNT:
   568  		s.writePacket(&packet.LevelEvent{
   569  			EventType: packet.LevelEventSoundFuse,
   570  			Position:  vec64To32(pos),
   571  		})
   572  		return
   573  	case sound.FireworkLaunch:
   574  		pk.SoundType = packet.SoundEventLaunch
   575  	case sound.FireworkHugeBlast:
   576  		pk.SoundType = packet.SoundEventLargeBlast
   577  	case sound.FireworkBlast:
   578  		pk.SoundType = packet.SoundEventBlast
   579  	case sound.FireworkTwinkle:
   580  		pk.SoundType = packet.SoundEventTwinkle
   581  	case sound.FurnaceCrackle:
   582  		pk.SoundType = packet.SoundEventFurnaceUse
   583  	case sound.BlastFurnaceCrackle:
   584  		pk.SoundType = packet.SoundEventBlastFurnaceUse
   585  	case sound.SmokerCrackle:
   586  		pk.SoundType = packet.SoundEventSmokerUse
   587  	case sound.UseSpyglass:
   588  		pk.SoundType = packet.SoundEventUseSpyglass
   589  	case sound.StopUsingSpyglass:
   590  		pk.SoundType = packet.SoundEventStopUsingSpyglass
   591  	case sound.GoatHorn:
   592  		switch so.Horn {
   593  		case sound.Ponder():
   594  			pk.SoundType = packet.SoundEventGoatCall0
   595  		case sound.Sing():
   596  			pk.SoundType = packet.SoundEventGoatCall1
   597  		case sound.Seek():
   598  			pk.SoundType = packet.SoundEventGoatCall2
   599  		case sound.Feel():
   600  			pk.SoundType = packet.SoundEventGoatCall3
   601  		case sound.Admire():
   602  			pk.SoundType = packet.SoundEventGoatCall4
   603  		case sound.Call():
   604  			pk.SoundType = packet.SoundEventGoatCall5
   605  		case sound.Yearn():
   606  			pk.SoundType = packet.SoundEventGoatCall6
   607  		case sound.Dream():
   608  			pk.SoundType = packet.SoundEventGoatCall7
   609  		}
   610  	case sound.FireExtinguish:
   611  		pk.SoundType = packet.SoundEventExtinguishFire
   612  	case sound.Ignite:
   613  		pk.SoundType = packet.SoundEventIgnite
   614  	case sound.Burning:
   615  		pk.SoundType = packet.SoundEventPlayerHurtOnFire
   616  	case sound.Drowning:
   617  		pk.SoundType = packet.SoundEventPlayerHurtDrown
   618  	case sound.Fall:
   619  		pk.EntityType = "minecraft:player"
   620  		if so.Distance > 4 {
   621  			pk.SoundType = packet.SoundEventFallBig
   622  			break
   623  		}
   624  		pk.SoundType = packet.SoundEventFallSmall
   625  	case sound.Burp:
   626  		pk.SoundType = packet.SoundEventBurp
   627  	case sound.DoorOpen:
   628  		pk.SoundType, pk.ExtraData = packet.SoundEventDoorOpen, int32(world.BlockRuntimeID(so.Block))
   629  	case sound.DoorClose:
   630  		pk.SoundType, pk.ExtraData = packet.SoundEventDoorClose, int32(world.BlockRuntimeID(so.Block))
   631  	case sound.TrapdoorOpen:
   632  		pk.SoundType, pk.ExtraData = packet.SoundEventTrapdoorOpen, int32(world.BlockRuntimeID(so.Block))
   633  	case sound.TrapdoorClose:
   634  		pk.SoundType, pk.ExtraData = packet.SoundEventTrapdoorClose, int32(world.BlockRuntimeID(so.Block))
   635  	case sound.FenceGateOpen:
   636  		pk.SoundType, pk.ExtraData = packet.SoundEventFenceGateOpen, int32(world.BlockRuntimeID(so.Block))
   637  	case sound.FenceGateClose:
   638  		pk.SoundType, pk.ExtraData = packet.SoundEventFenceGateClose, int32(world.BlockRuntimeID(so.Block))
   639  	case sound.Deny:
   640  		pk.SoundType = packet.SoundEventDeny
   641  	case sound.BlockPlace:
   642  		pk.SoundType, pk.ExtraData = packet.SoundEventPlace, int32(world.BlockRuntimeID(so.Block))
   643  	case sound.AnvilLand:
   644  		s.writePacket(&packet.LevelEvent{
   645  			EventType: packet.LevelEventSoundAnvilLand,
   646  			Position:  vec64To32(pos),
   647  		})
   648  		return
   649  	case sound.AnvilUse:
   650  		s.writePacket(&packet.LevelEvent{
   651  			EventType: packet.LevelEventSoundAnvilUsed,
   652  			Position:  vec64To32(pos),
   653  		})
   654  		return
   655  	case sound.AnvilBreak:
   656  		s.writePacket(&packet.LevelEvent{
   657  			EventType: packet.LevelEventSoundAnvilBroken,
   658  			Position:  vec64To32(pos),
   659  		})
   660  		return
   661  	case sound.ChestClose:
   662  		pk.SoundType = packet.SoundEventChestClosed
   663  	case sound.ChestOpen:
   664  		pk.SoundType = packet.SoundEventChestOpen
   665  	case sound.BarrelClose:
   666  		pk.SoundType = packet.SoundEventBarrelClose
   667  	case sound.BarrelOpen:
   668  		pk.SoundType = packet.SoundEventBarrelOpen
   669  	case sound.BlockBreaking:
   670  		pk.SoundType, pk.ExtraData = packet.SoundEventHit, int32(world.BlockRuntimeID(so.Block))
   671  	case sound.ItemBreak:
   672  		pk.SoundType = packet.SoundEventBreak
   673  	case sound.ItemUseOn:
   674  		pk.SoundType, pk.ExtraData = packet.SoundEventItemUseOn, int32(world.BlockRuntimeID(so.Block))
   675  	case sound.Fizz:
   676  		pk.SoundType = packet.SoundEventFizz
   677  	case sound.GlassBreak:
   678  		pk.SoundType = packet.SoundEventGlass
   679  	case sound.Attack:
   680  		pk.SoundType, pk.EntityType = packet.SoundEventAttackStrong, "minecraft:player"
   681  		if !so.Damage {
   682  			pk.SoundType = packet.SoundEventAttackNoDamage
   683  		}
   684  	case sound.BucketFill:
   685  		if _, water := so.Liquid.(block.Water); water {
   686  			pk.SoundType = packet.SoundEventBucketFillWater
   687  			break
   688  		}
   689  		pk.SoundType = packet.SoundEventBucketFillLava
   690  	case sound.BucketEmpty:
   691  		if _, water := so.Liquid.(block.Water); water {
   692  			pk.SoundType = packet.SoundEventBucketEmptyWater
   693  			break
   694  		}
   695  		pk.SoundType = packet.SoundEventBucketEmptyLava
   696  	case sound.BowShoot:
   697  		pk.SoundType = packet.SoundEventBow
   698  	case sound.ArrowHit:
   699  		pk.SoundType = packet.SoundEventBowHit
   700  	case sound.ItemThrow:
   701  		pk.SoundType, pk.EntityType = packet.SoundEventThrow, "minecraft:player"
   702  	case sound.LevelUp:
   703  		pk.SoundType, pk.ExtraData = packet.SoundEventLevelUp, 0x10000000
   704  	case sound.Experience:
   705  		s.writePacket(&packet.LevelEvent{
   706  			EventType: packet.LevelEventSoundExperienceOrbPickup,
   707  			Position:  vec64To32(pos),
   708  		})
   709  		return
   710  	case sound.MusicDiscPlay:
   711  		switch so.DiscType {
   712  		case sound.Disc13():
   713  			pk.SoundType = packet.SoundEventRecord13
   714  		case sound.DiscCat():
   715  			pk.SoundType = packet.SoundEventRecordCat
   716  		case sound.DiscBlocks():
   717  			pk.SoundType = packet.SoundEventRecordBlocks
   718  		case sound.DiscChirp():
   719  			pk.SoundType = packet.SoundEventRecordChirp
   720  		case sound.DiscFar():
   721  			pk.SoundType = packet.SoundEventRecordFar
   722  		case sound.DiscMall():
   723  			pk.SoundType = packet.SoundEventRecordMall
   724  		case sound.DiscMellohi():
   725  			pk.SoundType = packet.SoundEventRecordMellohi
   726  		case sound.DiscStal():
   727  			pk.SoundType = packet.SoundEventRecordStal
   728  		case sound.DiscStrad():
   729  			pk.SoundType = packet.SoundEventRecordStrad
   730  		case sound.DiscWard():
   731  			pk.SoundType = packet.SoundEventRecordWard
   732  		case sound.Disc11():
   733  			pk.SoundType = packet.SoundEventRecord11
   734  		case sound.DiscWait():
   735  			pk.SoundType = packet.SoundEventRecordWait
   736  		case sound.DiscOtherside():
   737  			pk.SoundType = packet.SoundEventRecordOtherside
   738  		case sound.DiscPigstep():
   739  			pk.SoundType = packet.SoundEventRecordPigstep
   740  		case sound.Disc5():
   741  			pk.SoundType = packet.SoundEventRecord5
   742  		case sound.DiscRelic():
   743  			pk.SoundType = packet.SoundEventRecordRelic
   744  		}
   745  	case sound.MusicDiscEnd:
   746  		pk.SoundType = packet.SoundEventRecordNull
   747  	case sound.FireCharge:
   748  		s.writePacket(&packet.LevelEvent{
   749  			EventType: packet.LevelEventSoundBlazeFireball,
   750  			Position:  vec64To32(pos),
   751  		})
   752  		return
   753  	case sound.ComposterEmpty:
   754  		pk.SoundType = packet.SoundEventComposterEmpty
   755  	case sound.ComposterFill:
   756  		pk.SoundType = packet.SoundEventComposterFill
   757  	case sound.ComposterFillLayer:
   758  		pk.SoundType = packet.SoundEventComposterFillLayer
   759  	case sound.ComposterReady:
   760  		pk.SoundType = packet.SoundEventComposterReady
   761  	case sound.LecternBookPlace:
   762  		pk.SoundType = packet.SoundEventLecternBookPlace
   763  	}
   764  	s.writePacket(pk)
   765  }
   766  
   767  // PlaySound plays a world.Sound to the client. The volume is not dependent on the distance to the source if it is a
   768  // sound of the LevelSoundEvent packet.
   769  func (s *Session) PlaySound(t world.Sound) {
   770  	if s == Nop {
   771  		return
   772  	}
   773  	s.playSound(entity.EyePosition(s.c), t, true)
   774  }
   775  
   776  // ViewSound ...
   777  func (s *Session) ViewSound(pos mgl64.Vec3, soundType world.Sound) {
   778  	s.playSound(pos, soundType, false)
   779  }
   780  
   781  // OpenSign ...
   782  func (s *Session) OpenSign(pos cube.Pos, frontSide bool) {
   783  	blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
   784  	s.writePacket(&packet.OpenSign{
   785  		Position:  blockPos,
   786  		FrontSide: frontSide,
   787  	})
   788  }
   789  
   790  // ViewFurnaceUpdate updates a furnace for the associated session based on previous times.
   791  func (s *Session) ViewFurnaceUpdate(prevCookTime, cookTime, prevRemainingFuelTime, remainingFuelTime, prevMaxFuelTime, maxFuelTime time.Duration) {
   792  	if prevCookTime != cookTime {
   793  		s.writePacket(&packet.ContainerSetData{
   794  			WindowID: byte(s.openedWindowID.Load()),
   795  			Key:      packet.ContainerDataFurnaceTickCount,
   796  			Value:    int32(cookTime.Milliseconds() / 50),
   797  		})
   798  	}
   799  
   800  	if prevRemainingFuelTime != remainingFuelTime {
   801  		s.writePacket(&packet.ContainerSetData{
   802  			WindowID: byte(s.openedWindowID.Load()),
   803  			Key:      packet.ContainerDataFurnaceLitTime,
   804  			Value:    int32(remainingFuelTime.Milliseconds() / 50),
   805  		})
   806  	}
   807  
   808  	if prevMaxFuelTime != maxFuelTime {
   809  		s.writePacket(&packet.ContainerSetData{
   810  			WindowID: byte(s.openedWindowID.Load()),
   811  			Key:      packet.ContainerDataFurnaceLitDuration,
   812  			Value:    int32(maxFuelTime.Milliseconds() / 50),
   813  		})
   814  	}
   815  }
   816  
   817  // ViewBlockUpdate ...
   818  func (s *Session) ViewBlockUpdate(pos cube.Pos, b world.Block, layer int) {
   819  	blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
   820  	s.writePacket(&packet.UpdateBlock{
   821  		Position:          blockPos,
   822  		NewBlockRuntimeID: world.BlockRuntimeID(b),
   823  		Flags:             packet.BlockUpdateNetwork,
   824  		Layer:             uint32(layer),
   825  	})
   826  	if v, ok := b.(world.NBTer); ok {
   827  		NBTData := v.EncodeNBT()
   828  		NBTData["x"], NBTData["y"], NBTData["z"] = int32(pos.X()), int32(pos.Y()), int32(pos.Z())
   829  		s.writePacket(&packet.BlockActorData{
   830  			Position: blockPos,
   831  			NBTData:  NBTData,
   832  		})
   833  	}
   834  }
   835  
   836  // ViewEntityAction ...
   837  func (s *Session) ViewEntityAction(e world.Entity, a world.EntityAction) {
   838  	switch act := a.(type) {
   839  	case entity.SwingArmAction:
   840  		if _, ok := e.(Controllable); ok {
   841  			if s.entityRuntimeID(e) == selfEntityRuntimeID && s.swingingArm.Load() {
   842  				return
   843  			}
   844  			s.writePacket(&packet.Animate{
   845  				ActionType:      packet.AnimateActionSwingArm,
   846  				EntityRuntimeID: s.entityRuntimeID(e),
   847  			})
   848  			return
   849  		}
   850  		s.writePacket(&packet.ActorEvent{
   851  			EntityRuntimeID: s.entityRuntimeID(e),
   852  			EventType:       packet.ActorEventStartAttacking,
   853  		})
   854  	case entity.HurtAction:
   855  		s.writePacket(&packet.ActorEvent{
   856  			EntityRuntimeID: s.entityRuntimeID(e),
   857  			EventType:       packet.ActorEventHurt,
   858  		})
   859  	case entity.CriticalHitAction:
   860  		s.writePacket(&packet.Animate{
   861  			ActionType:      packet.AnimateActionCriticalHit,
   862  			EntityRuntimeID: s.entityRuntimeID(e),
   863  		})
   864  	case entity.DeathAction:
   865  		s.writePacket(&packet.ActorEvent{
   866  			EntityRuntimeID: s.entityRuntimeID(e),
   867  			EventType:       packet.ActorEventDeath,
   868  		})
   869  	case entity.PickedUpAction:
   870  		s.writePacket(&packet.TakeItemActor{
   871  			ItemEntityRuntimeID:  s.entityRuntimeID(e),
   872  			TakerEntityRuntimeID: s.entityRuntimeID(act.Collector),
   873  		})
   874  	case entity.ArrowShakeAction:
   875  		s.writePacket(&packet.ActorEvent{
   876  			EntityRuntimeID: s.entityRuntimeID(e),
   877  			EventType:       packet.ActorEventShake,
   878  			EventData:       int32(act.Duration.Milliseconds() / 50),
   879  		})
   880  	case entity.FireworkExplosionAction:
   881  		s.writePacket(&packet.ActorEvent{
   882  			EntityRuntimeID: s.entityRuntimeID(e),
   883  			EventType:       packet.ActorEventFireworksExplode,
   884  		})
   885  	case entity.EatAction:
   886  		if user, ok := e.(item.User); ok {
   887  			held, _ := user.HeldItems()
   888  			it := held.Item()
   889  			if held.Empty() {
   890  				// This can happen sometimes if the user switches between items very quickly, so just ignore the action.
   891  				return
   892  			}
   893  			if _, ok := it.(item.Consumable); !ok {
   894  				// Not consumable, refer to the comment above.
   895  				return
   896  			}
   897  			rid, meta, _ := world.ItemRuntimeID(it)
   898  			s.writePacket(&packet.ActorEvent{
   899  				EntityRuntimeID: s.entityRuntimeID(e),
   900  				EventType:       packet.ActorEventFeed,
   901  				// It's a little weird how the runtime ID is still shifted 16 bits to the left here, given the
   902  				// runtime ID already includes the meta, but it seems to work.
   903  				EventData: (rid << 16) | int32(meta),
   904  			})
   905  		}
   906  	}
   907  }
   908  
   909  // ViewEntityState ...
   910  func (s *Session) ViewEntityState(e world.Entity) {
   911  	s.writePacket(&packet.SetActorData{
   912  		EntityRuntimeID: s.entityRuntimeID(e),
   913  		EntityMetadata:  s.parseEntityMetadata(e),
   914  	})
   915  }
   916  
   917  // ViewEntityAnimation ...
   918  func (s *Session) ViewEntityAnimation(e world.Entity, animationName string) {
   919  	s.writePacket(&packet.AnimateEntity{
   920  		Animation: animationName,
   921  		EntityRuntimeIDs: []uint64{
   922  			s.entityRuntimeID(e),
   923  		},
   924  	})
   925  }
   926  
   927  // OpenBlockContainer ...
   928  func (s *Session) OpenBlockContainer(pos cube.Pos) {
   929  	if s.containerOpened.Load() && s.openedPos.Load() == pos {
   930  		return
   931  	}
   932  	s.closeCurrentContainer()
   933  
   934  	w := s.c.World()
   935  	b := w.Block(pos)
   936  	if container, ok := b.(block.Container); ok {
   937  		s.openNormalContainer(container, pos)
   938  		return
   939  	}
   940  	// We hit a special kind of window like beacons, which are not actually opened server-side.
   941  	nextID := s.nextWindowID()
   942  	s.containerOpened.Store(true)
   943  	s.openedWindow.Store(inventory.New(1, nil))
   944  	s.openedPos.Store(pos)
   945  
   946  	var containerType byte
   947  	switch b := b.(type) {
   948  	case block.CraftingTable:
   949  		containerType = protocol.ContainerTypeWorkbench
   950  	case block.EnchantingTable:
   951  		containerType = protocol.ContainerTypeEnchantment
   952  	case block.Anvil:
   953  		containerType = protocol.ContainerTypeAnvil
   954  	case block.Beacon:
   955  		containerType = protocol.ContainerTypeBeacon
   956  	case block.Loom:
   957  		containerType = protocol.ContainerTypeLoom
   958  	case block.Grindstone:
   959  		containerType = protocol.ContainerTypeGrindstone
   960  	case block.Stonecutter:
   961  		containerType = protocol.ContainerTypeStonecutter
   962  	case block.SmithingTable:
   963  		containerType = protocol.ContainerTypeSmithingTable
   964  	case block.EnderChest:
   965  		b.AddViewer(w, pos)
   966  
   967  		inv := s.c.EnderChestInventory()
   968  		s.openedWindow.Store(inv)
   969  
   970  		defer s.sendInv(inv, uint32(nextID))
   971  	}
   972  
   973  	s.openedContainerID.Store(uint32(containerType))
   974  	s.writePacket(&packet.ContainerOpen{
   975  		WindowID:                nextID,
   976  		ContainerType:           containerType,
   977  		ContainerPosition:       protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
   978  		ContainerEntityUniqueID: -1,
   979  	})
   980  }
   981  
   982  // openNormalContainer opens a normal container that can hold items in it server-side.
   983  func (s *Session) openNormalContainer(b block.Container, pos cube.Pos) {
   984  	b.AddViewer(s, s.c.World(), pos)
   985  
   986  	nextID := s.nextWindowID()
   987  	s.containerOpened.Store(true)
   988  	s.openedWindow.Store(b.Inventory())
   989  	s.openedPos.Store(pos)
   990  
   991  	var containerType byte
   992  	switch b.(type) {
   993  	case block.Furnace:
   994  		containerType = protocol.ContainerTypeFurnace
   995  	case block.BlastFurnace:
   996  		containerType = protocol.ContainerTypeBlastFurnace
   997  	case block.Smoker:
   998  		containerType = protocol.ContainerTypeSmoker
   999  	}
  1000  
  1001  	s.writePacket(&packet.ContainerOpen{
  1002  		WindowID:                nextID,
  1003  		ContainerType:           containerType,
  1004  		ContainerPosition:       protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])},
  1005  		ContainerEntityUniqueID: -1,
  1006  	})
  1007  	s.sendInv(b.Inventory(), uint32(nextID))
  1008  }
  1009  
  1010  // ViewSlotChange ...
  1011  func (s *Session) ViewSlotChange(slot int, newItem item.Stack) {
  1012  	if !s.containerOpened.Load() {
  1013  		return
  1014  	}
  1015  	if s.inTransaction.Load() {
  1016  		// Don't send slot changes to the player itself.
  1017  		return
  1018  	}
  1019  	s.writePacket(&packet.InventorySlot{
  1020  		WindowID: s.openedWindowID.Load(),
  1021  		Slot:     uint32(slot),
  1022  		NewItem:  instanceFromItem(newItem),
  1023  	})
  1024  }
  1025  
  1026  // ViewBlockAction ...
  1027  func (s *Session) ViewBlockAction(pos cube.Pos, a world.BlockAction) {
  1028  	blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
  1029  	switch t := a.(type) {
  1030  	case block.OpenAction:
  1031  		s.writePacket(&packet.BlockEvent{
  1032  			Position:  blockPos,
  1033  			EventType: packet.BlockEventChangeChestState,
  1034  			EventData: 1,
  1035  		})
  1036  	case block.CloseAction:
  1037  		s.writePacket(&packet.BlockEvent{
  1038  			Position:  blockPos,
  1039  			EventType: packet.BlockEventChangeChestState,
  1040  		})
  1041  	case block.StartCrackAction:
  1042  		s.writePacket(&packet.LevelEvent{
  1043  			EventType: packet.LevelEventStartBlockCracking,
  1044  			Position:  vec64To32(pos.Vec3()),
  1045  			EventData: int32(65535 / (t.BreakTime.Seconds() * 20)),
  1046  		})
  1047  	case block.StopCrackAction:
  1048  		s.writePacket(&packet.LevelEvent{
  1049  			EventType: packet.LevelEventStopBlockCracking,
  1050  			Position:  vec64To32(pos.Vec3()),
  1051  			EventData: 0,
  1052  		})
  1053  	case block.ContinueCrackAction:
  1054  		s.writePacket(&packet.LevelEvent{
  1055  			EventType: packet.LevelEventUpdateBlockCracking,
  1056  			Position:  vec64To32(pos.Vec3()),
  1057  			EventData: int32(65535 / (t.BreakTime.Seconds() * 20)),
  1058  		})
  1059  	}
  1060  }
  1061  
  1062  // ViewEmote ...
  1063  func (s *Session) ViewEmote(player world.Entity, emote uuid.UUID) {
  1064  	s.writePacket(&packet.Emote{
  1065  		EntityRuntimeID: s.entityRuntimeID(player),
  1066  		EmoteID:         emote.String(),
  1067  		Flags:           packet.EmoteFlagServerSide,
  1068  	})
  1069  }
  1070  
  1071  // ViewSkin ...
  1072  func (s *Session) ViewSkin(e world.Entity) {
  1073  	switch v := e.(type) {
  1074  	case Controllable:
  1075  		s.writePacket(&packet.PlayerSkin{
  1076  			UUID: v.UUID(),
  1077  			Skin: skinToProtocol(v.Skin()),
  1078  		})
  1079  	}
  1080  }
  1081  
  1082  // ViewWorldSpawn ...
  1083  func (s *Session) ViewWorldSpawn(pos cube.Pos) {
  1084  	blockPos := protocol.BlockPos{int32(pos[0]), int32(pos[1]), int32(pos[2])}
  1085  	s.writePacket(&packet.SetSpawnPosition{
  1086  		SpawnType:     packet.SpawnTypeWorld,
  1087  		Position:      blockPos,
  1088  		Dimension:     packet.DimensionOverworld,
  1089  		SpawnPosition: blockPos,
  1090  	})
  1091  }
  1092  
  1093  // ViewWeather ...
  1094  func (s *Session) ViewWeather(raining, thunder bool) {
  1095  	pk := &packet.LevelEvent{
  1096  		EventType: packet.LevelEventStopRaining,
  1097  	}
  1098  	if raining {
  1099  		pk.EventType, pk.EventData = packet.LevelEventStartRaining, int32(rand.Intn(50000)+10000)
  1100  	}
  1101  	s.writePacket(pk)
  1102  
  1103  	pk = &packet.LevelEvent{
  1104  		EventType: packet.LevelEventStopThunderstorm,
  1105  	}
  1106  	if thunder {
  1107  		pk.EventType, pk.EventData = packet.LevelEventStartThunderstorm, int32(rand.Intn(50000)+10000)
  1108  	}
  1109  	s.writePacket(pk)
  1110  }
  1111  
  1112  // nextWindowID produces the next window ID for a new window. It is an int of 1-99.
  1113  func (s *Session) nextWindowID() byte {
  1114  	if s.openedWindowID.CAS(99, 1) {
  1115  		return 1
  1116  	}
  1117  	return byte(s.openedWindowID.Add(1))
  1118  }
  1119  
  1120  // closeWindow closes the container window currently opened. If no window is open, closeWindow will do
  1121  // nothing.
  1122  func (s *Session) closeWindow() {
  1123  	if !s.containerOpened.CAS(true, false) {
  1124  		return
  1125  	}
  1126  	s.openedContainerID.Store(0)
  1127  	s.openedWindow.Store(inventory.New(1, nil))
  1128  	s.writePacket(&packet.ContainerClose{WindowID: byte(s.openedWindowID.Load())})
  1129  }
  1130  
  1131  // entityRuntimeID returns the runtime ID of the entity passed.
  1132  // noinspection GoCommentLeadingSpace
  1133  func (s *Session) entityRuntimeID(e world.Entity) uint64 {
  1134  	s.entityMutex.RLock()
  1135  	//lint:ignore S1005 Double assignment is done explicitly to prevent panics.
  1136  	id, _ := s.entityRuntimeIDs[e]
  1137  	s.entityMutex.RUnlock()
  1138  	return id
  1139  }
  1140  
  1141  // entityFromRuntimeID attempts to return an entity by its runtime ID. False is returned if no entity with the
  1142  // ID could be found.
  1143  func (s *Session) entityFromRuntimeID(id uint64) (world.Entity, bool) {
  1144  	s.entityMutex.RLock()
  1145  	e, ok := s.entities[id]
  1146  	s.entityMutex.RUnlock()
  1147  	return e, ok
  1148  }
  1149  
  1150  // vec32To64 converts a mgl32.Vec3 to a mgl64.Vec3.
  1151  func vec32To64(vec3 mgl32.Vec3) mgl64.Vec3 {
  1152  	return mgl64.Vec3{float64(vec3[0]), float64(vec3[1]), float64(vec3[2])}
  1153  }
  1154  
  1155  // vec64To32 converts a mgl64.Vec3 to a mgl32.Vec3.
  1156  func vec64To32(vec3 mgl64.Vec3) mgl32.Vec3 {
  1157  	return mgl32.Vec3{float32(vec3[0]), float32(vec3[1]), float32(vec3[2])}
  1158  }
  1159  
  1160  // blockPosFromProtocol ...
  1161  func blockPosFromProtocol(pos protocol.BlockPos) cube.Pos {
  1162  	return cube.Pos{int(pos.X()), int(pos.Y()), int(pos.Z())}
  1163  }
  1164  
  1165  // boolByte returns 1 if the bool passed is true, or 0 if it is false.
  1166  func boolByte(b bool) uint8 {
  1167  	if b {
  1168  		return 1
  1169  	}
  1170  	return 0
  1171  }
  1172  
  1173  // abs ...
  1174  func abs(a int) int {
  1175  	if a < 0 {
  1176  		return -a
  1177  	}
  1178  	return a
  1179  }