func (w *World) Spawn() cube.Pos {
	if w == nil {
		return cube.Pos{}
	}
	w.mu.Lock()
	s := w.set.Spawn
	w.mu.Unlock()
	if s[1] > cube.MaxY {
		s[1] = w.highestObstructingBlock(s[0], s[2]) + 1
	}
	return s
}
Spawn returns the spawn of the world. Every new player will by default spawn on this position in the world
when joining.
package world
import (
	"fmt"
	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/entity/physics"
	"github.com/df-mc/dragonfly/server/internal"
	"github.com/df-mc/dragonfly/server/world/chunk"
	"github.com/go-gl/mathgl/mgl64"
	"go.uber.org/atomic"
	"math/rand"
	"sync"
	"time"
)
type World struct {
	log internal.Logger
	mu   sync.Mutex
	set  Settings
	prov Provider
	rdonly atomic.Bool
	lastPos   ChunkPos
	lastChunk *chunkData
	closing chan struct{}
	running sync.WaitGroup
	handlerMu sync.RWMutex
	handler   Handler
	genMu sync.RWMutex
	gen   Generator
	chunkMu sync.Mutex
	chunks map[ChunkPos]*chunkData
	entityMu sync.RWMutex
	entities map[Entity]ChunkPos
	r         *rand.Rand
	simDistSq int32
	randomTickSpeed atomic.Uint32
	updateMu sync.Mutex
	blockUpdates             map[cube.Pos]int64
	updatePositions          []cube.Pos
	neighbourUpdatePositions []neighbourUpdate
	neighbourUpdatesSync     []neighbourUpdate
	toTick              []toTick
	blockEntitiesToTick []blockEntityToTick
	positionCache       []ChunkPos
	entitiesToTick      []TickerEntity
	viewersMu sync.Mutex
	viewers   map[Viewer]struct{}
}
func New(log internal.Logger, simulationDistance int) *World {
	w := &World{
		r:               rand.New(rand.NewSource(time.Now().Unix())),
		blockUpdates:    map[cube.Pos]int64{},
		entities:        map[Entity]ChunkPos{},
		viewers:         map[Viewer]struct{}{},
		prov:            NoIOProvider{},
		gen:             NopGenerator{},
		handler:         NopHandler{},
		simDistSq:       int32(simulationDistance * simulationDistance),
		randomTickSpeed: *atomic.NewUint32(3),
		log:             log,
		set:             defaultSettings(),
		closing:         make(chan struct{}),
	}
	w.initChunkCache()
	go w.startTicking()
	go w.chunkCacheJanitor()
	return w
}
func (w *World) Name() string {
	w.mu.Lock()
	defer w.mu.Unlock()
	return w.set.Name
}
func (w *World) Block(pos cube.Pos) Block {
	if w == nil || pos.OutOfBounds() {
		return air()
	}
	chunkPos := ChunkPos{int32(pos[0] >> 4), int32(pos[2] >> 4)}
	c, err := w.chunk(chunkPos)
	if err != nil {
		w.log.Errorf("error getting block: %v", err)
		return air()
	}
	rid := c.RuntimeID(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0)
	b, _ := BlockByRuntimeID(rid)
	if nbtBlocks[rid] {
		if nbtB, ok := c.e[pos]; ok {
			c.Unlock()
			return nbtB
		}
	}
	c.Unlock()
	return b
}
func (w *World) blockInChunk(c *chunkData, pos cube.Pos) (Block, error) {
	if pos.OutOfBounds() {
		return air(), nil
	}
	rid := c.RuntimeID(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0)
	b, _ := BlockByRuntimeID(rid)
	if nbtBlocks[rid] {
		b, ok := c.e[pos]
		if ok {
			return b, nil
		}
	}
	return b, nil
}
func runtimeID(w *World, pos cube.Pos) uint32 {
	if w == nil || pos.OutOfBounds() {
		return airRID
	}
	c, err := w.chunk(ChunkPos{int32(pos[0] >> 4), int32(pos[2] >> 4)})
	if err != nil {
		return airRID
	}
	rid := c.RuntimeID(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0)
	c.Unlock()
	return rid
}
func (w *World) HighestLightBlocker(x, z int) int16 {
	if w == nil {
		return cube.MinY
	}
	c, err := w.chunk(ChunkPos{int32(x >> 4), int32(z >> 4)})
	if err != nil {
		return cube.MinY
	}
	v := c.HighestLightBlocker(uint8(x), uint8(z))
	c.Unlock()
	return v
}
func (w *World) HighestBlock(x, z int) int {
	if w == nil {
		return cube.MinY
	}
	c, err := w.chunk(ChunkPos{int32(x >> 4), int32(z >> 4)})
	if err != nil {
		return cube.MinY
	}
	v := c.HighestBlock(uint8(x), uint8(z))
	c.Unlock()
	return int(v)
}
func (w *World) highestObstructingBlock(x, z int) int {
	if w == nil {
		return 0
	}
	yHigh := w.HighestBlock(x, z)
	for y := yHigh; y >= cube.MinY; y-- {
		pos := cube.Pos{x, y, z}
		m := w.Block(pos).Model()
		if m.FaceSolid(pos, cube.FaceUp, w) || m.FaceSolid(pos, cube.FaceDown, w) {
			return y
		}
	}
	return cube.MinY
}
func (w *World) SetBlock(pos cube.Pos, b Block) {
	if w == nil || pos.OutOfBounds() {
		return
	}
	x, z := int32(pos[0]>>4), int32(pos[2]>>4)
	c, err := w.chunk(ChunkPos{x, z})
	if err != nil {
		return
	}
	rid, ok := BlockRuntimeID(b)
	if !ok {
		c.Unlock()
		w.log.Errorf("runtime ID of block %+v not found", b)
		return
	}
	c.SetRuntimeID(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 0, rid)
	if nbtBlocks[rid] {
		c.e[pos] = b
	} else {
		delete(c.e, pos)
	}
	var viewers []Viewer
	if len(c.v) > 0 {
		viewers = make([]Viewer, len(c.v))
		copy(viewers, c.v)
	}
	c.Unlock()
	for _, viewer := range viewers {
		viewer.ViewBlockUpdate(pos, b, 0)
	}
}
var breakParticle func(b Block) Particle
func (w *World) BreakBlock(pos cube.Pos) {
	if w == nil {
		return
	}
	old := w.Block(pos)
	w.SetBlock(pos, nil)
	w.AddParticle(pos.Vec3Centre(), breakParticle(old))
	if liq, ok := w.Liquid(pos); ok {
		w.SetLiquid(pos, liq)
	} else {
		w.doBlockUpdatesAround(pos)
	}
}
func (w *World) BreakBlockWithoutParticles(pos cube.Pos) {
	if w == nil {
		return
	}
	w.SetBlock(pos, nil)
	if liq, ok := w.Liquid(pos); ok {
		w.SetLiquid(pos, liq)
	} else {
		w.doBlockUpdatesAround(pos)
	}
}
func (w *World) PlaceBlock(pos cube.Pos, b Block) {
	if w == nil {
		return
	}
	var liquid Liquid
	if displacer, ok := b.(LiquidDisplacer); ok {
		liq, ok := w.Liquid(pos)
		if ok && displacer.CanDisplace(liq) && liq.LiquidDepth() == 8 {
			liquid = liq
		}
	}
	w.SetBlock(pos, b)
	w.SetLiquid(pos, liquid)
}
func (w *World) BuildStructure(pos cube.Pos, s Structure) {
	if w == nil {
		return
	}
	dim := s.Dimensions()
	width, height, length := dim[0], dim[1], dim[2]
	maxX, maxY, maxZ := pos[0]+width, pos[1]+height, pos[2]+length
	for chunkX := pos[0] >> 4; chunkX < (maxX>>4)+1; chunkX++ {
		for chunkZ := pos[2] >> 4; chunkZ < (maxZ>>4)+1; chunkZ++ {
			chunkPos := ChunkPos{int32(chunkX), int32(chunkZ)}
			c, err := w.chunk(chunkPos)
			if err != nil {
				w.log.Errorf("error loading chunk for structure: %v", err)
				continue
			}
			f := func(x, y, z int) Block {
				actualX, actualZ := chunkX+x, chunkZ+z
				if actualX>>4 == chunkX && actualZ>>4 == chunkZ {
					b, _ := w.blockInChunk(c, cube.Pos{actualX, y, actualZ})
					return b
				}
				return w.Block(cube.Pos{actualX, y, actualZ})
			}
			baseX, baseZ := chunkX<<4, chunkZ<<4
			subs := c.Sub()
			for i, sub := range subs {
				baseY := i << 4
				if sub == nil {
					c.SetRuntimeID(0, int16(baseY), 0, 0, airRID)
					sub = subs[i]
				}
				if i < pos[1]>>4 {
					continue
				} else if baseY >= maxY {
					break
				}
				for localY := 0; localY < 16; localY++ {
					yOffset := baseY + localY
					if yOffset > cube.MaxY || yOffset >= maxY {
						break
					} else if yOffset < cube.MinY || yOffset < pos[1] {
						continue
					}
					for localX := 0; localX < 16; localX++ {
						xOffset := baseX + localX
						if xOffset < pos[0] || xOffset >= maxX {
							continue
						}
						for localZ := 0; localZ < 16; localZ++ {
							zOffset := baseZ + localZ
							if zOffset < pos[2] || zOffset >= maxZ {
								continue
							}
							b, liq := s.At(xOffset-pos[0], yOffset-pos[1], zOffset-pos[2], f)
							if b != nil {
								rid, ok := BlockRuntimeID(b)
								if !ok {
									w.log.Errorf("error setting block of structure: runtime ID of block state %+v not found", b)
									continue
								}
								sub.SetRuntimeID(uint8(xOffset), uint8(yOffset), uint8(zOffset), 0, rid)
								if nbtBlocks[rid] {
									c.e[pos] = b
								} else {
									delete(c.e, pos)
								}
							} else {
								sub.SetRuntimeID(uint8(xOffset), uint8(yOffset), uint8(zOffset), 0, airRID)
							}
							if liq != nil {
								rid, ok := BlockRuntimeID(liq)
								if !ok {
									w.log.Errorf("runtime ID of block state %+v not found", liq)
									continue
								}
								sub.SetRuntimeID(uint8(xOffset), uint8(yOffset), uint8(zOffset), 1, rid)
							} else {
								if len(sub.Layers()) < 2 {
									continue
								}
								sub.SetRuntimeID(uint8(xOffset), uint8(yOffset), uint8(zOffset), 1, airRID)
							}
						}
					}
				}
			}
			for _, viewer := range c.v {
				viewer.ViewChunk(chunkPos, c.Chunk, c.e)
			}
			c.Unlock()
		}
	}
}
func (w *World) Liquid(pos cube.Pos) (Liquid, bool) {
	if w == nil || pos.OutOfBounds() {
		return nil, false
	}
	c, err := w.chunk(chunkPosFromBlockPos(pos))
	if err != nil {
		w.log.Errorf("failed getting liquid: error getting chunk at position %v: %v", chunkPosFromBlockPos(pos), err)
		return nil, false
	}
	x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2])
	id := c.RuntimeID(x, y, z, 0)
	b, ok := BlockByRuntimeID(id)
	if !ok {
		w.log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id)
		c.Unlock()
		return nil, false
	}
	if liq, ok := b.(Liquid); ok {
		c.Unlock()
		return liq, true
	}
	id = c.RuntimeID(x, y, z, 1)
	b, ok = BlockByRuntimeID(id)
	c.Unlock()
	if !ok {
		w.log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id)
		return nil, false
	}
	if liq, ok := b.(Liquid); ok {
		return liq, true
	}
	return nil, false
}
func (w *World) SetLiquid(pos cube.Pos, b Liquid) {
	if w == nil || pos.OutOfBounds() {
		return
	}
	chunkPos := chunkPosFromBlockPos(pos)
	c, err := w.chunk(chunkPos)
	if err != nil {
		w.log.Errorf("failed setting liquid: error getting chunk at position %v: %v", chunkPosFromBlockPos(pos), err)
		return
	}
	if b == nil {
		w.removeLiquids(c, pos)
		c.Unlock()
		w.doBlockUpdatesAround(pos)
		return
	}
	x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2])
	if !replaceable(w, c, pos, b) {
		current, err := w.blockInChunk(c, pos)
		if err != nil {
			c.Unlock()
			w.log.Errorf("failed setting liquid: error getting block at position %v: %v", chunkPosFromBlockPos(pos), err)
			return
		}
		if displacer, ok := current.(LiquidDisplacer); !ok || !displacer.CanDisplace(b) {
			c.Unlock()
			return
		}
	}
	runtimeID, ok := BlockRuntimeID(b)
	if !ok {
		c.Unlock()
		w.log.Errorf("failed setting liquid: runtime ID of block state %+v not found", b)
		return
	}
	if w.removeLiquids(c, pos) {
		c.SetRuntimeID(x, y, z, 0, runtimeID)
		for _, v := range c.v {
			v.ViewBlockUpdate(pos, b, 0)
		}
	} else {
		c.SetRuntimeID(x, y, z, 1, runtimeID)
		for _, v := range c.v {
			v.ViewBlockUpdate(pos, b, 1)
		}
	}
	c.Unlock()
	w.doBlockUpdatesAround(pos)
}
func (w *World) removeLiquids(c *chunkData, pos cube.Pos) bool {
	x, y, z := uint8(pos[0]), int16(pos[1]), uint8(pos[2])
	noneLeft := false
	if noLeft, changed := w.removeLiquidOnLayer(c.Chunk, x, y, z, 0); noLeft {
		if changed {
			for _, v := range c.v {
				v.ViewBlockUpdate(pos, air(), 0)
			}
		}
		noneLeft = true
	}
	if _, changed := w.removeLiquidOnLayer(c.Chunk, x, y, z, 1); changed {
		for _, v := range c.v {
			v.ViewBlockUpdate(pos, air(), 1)
		}
	}
	return noneLeft
}
func (w *World) removeLiquidOnLayer(c *chunk.Chunk, x uint8, y int16, z, layer uint8) (bool, bool) {
	id := c.RuntimeID(x, y, z, layer)
	b, ok := BlockByRuntimeID(id)
	if !ok {
		w.log.Errorf("failed removing liquids: cannot get block by runtime ID %v", id)
		return false, false
	}
	if _, ok := b.(Liquid); ok {
		c.SetRuntimeID(x, y, z, layer, airRID)
		return true, true
	}
	return id == airRID, false
}
func (w *World) additionalLiquid(pos cube.Pos) (Liquid, bool) {
	if pos.OutOfBounds() {
		return nil, false
	}
	c, err := w.chunk(chunkPosFromBlockPos(pos))
	if err != nil {
		w.log.Errorf("failed getting liquid: error getting chunk at position %v: %v", chunkPosFromBlockPos(pos), err)
		return nil, false
	}
	id := c.RuntimeID(uint8(pos[0]), int16(pos[1]), uint8(pos[2]), 1)
	c.Unlock()
	b, ok := BlockByRuntimeID(id)
	if !ok {
		w.log.Errorf("failed getting liquid: cannot get block by runtime ID %v", id)
		return nil, false
	}
	liq, ok := b.(Liquid)
	return liq, ok
}
func (w *World) Light(pos cube.Pos) uint8 {
	if w == nil || pos[1] < cube.MinY {
		return 0
	}
	if pos[1] > cube.MaxY {
		return 15
	}
	c, err := w.chunk(chunkPosFromBlockPos(pos))
	if err != nil {
		return 0
	}
	l := c.Light(uint8(pos[0]), int16(pos[1]), uint8(pos[2]))
	c.Unlock()
	return l
}
func (w *World) SkyLight(pos cube.Pos) uint8 {
	if w == nil || pos[1] < cube.MinY {
		return 0
	}
	if pos[1] > cube.MaxY {
		return 15
	}
	c, err := w.chunk(chunkPosFromBlockPos(pos))
	if err != nil {
		return 0
	}
	l := c.SkyLight(uint8(pos[0]), int16(pos[1]), uint8(pos[2]))
	c.Unlock()
	return l
}
func (w *World) Time() int {
	if w == nil {
		return 0
	}
	w.mu.Lock()
	defer w.mu.Unlock()
	return int(w.set.Time)
}
func (w *World) SetTime(new int) {
	if w == nil {
		return
	}
	w.mu.Lock()
	w.set.Time = int64(new)
	w.mu.Unlock()
	for _, viewer := range w.allViewers() {
		viewer.ViewTime(new)
	}
}
func (w *World) StopTime() {
	w.enableTimeCycle(false)
}
func (w *World) StartTime() {
	w.enableTimeCycle(true)
}
func (w *World) enableTimeCycle(v bool) {
	if w == nil {
		return
	}
	w.mu.Lock()
	defer w.mu.Unlock()
	w.set.TimeCycle = v
}
func (w *World) StopWeatherCycle() {
	if w == nil {
		return
	}
	w.mu.Lock()
	defer w.mu.Unlock()
	w.set.WeatherCycle = false
}
func (w *World) StartWeatherCycle() {
	if w == nil {
		return
	}
	w.mu.Lock()
	defer w.mu.Unlock()
	w.set.WeatherCycle = true
}
func (w *World) RainingAt(pos cube.Pos) bool {
	if w == nil {
		return false
	}
	w.mu.Lock()
	a := w.set.Raining
	w.mu.Unlock()
	return a && w.highestObstructingBlock(pos[0], pos[2]) < pos[1]
}
func (w *World) ThunderingAt(pos cube.Pos) bool {
	if w == nil {
		return false
	}
	w.mu.Lock()
	a := w.set.Thundering && w.set.Raining
	w.mu.Unlock()
	return a && w.highestObstructingBlock(pos[0], pos[2]) < pos[1]
}
func (w *World) AddParticle(pos mgl64.Vec3, p Particle) {
	if w == nil {
		return
	}
	p.Spawn(w, pos)
	for _, viewer := range w.Viewers(pos) {
		viewer.ViewParticle(pos, p)
	}
}
func (w *World) PlaySound(pos mgl64.Vec3, s Sound) {
	for _, viewer := range w.Viewers(pos) {
		viewer.ViewSound(pos, s)
	}
}
var (
	worldsMu sync.RWMutex
	entityWorlds = map[Entity]*World{}
)
func (w *World) AddEntity(e Entity) {
	if w == nil {
		return
	}
	if e.World() != nil {
		e.World().RemoveEntity(e)
	}
	worldsMu.Lock()
	entityWorlds[e] = w
	worldsMu.Unlock()
	chunkPos := chunkPosFromVec3(e.Position())
	w.entityMu.Lock()
	w.entities[e] = chunkPos
	w.entityMu.Unlock()
	c, err := w.chunk(chunkPos)
	if err != nil {
		w.log.Errorf("error loading chunk to add entity: %v", err)
		return
	}
	c.entities = append(c.entities, e)
	var viewers []Viewer
	if len(c.v) > 0 {
		viewers = make([]Viewer, len(c.v))
		copy(viewers, c.v)
	}
	c.Unlock()
	for _, viewer := range viewers {
		showEntity(e, viewer)
	}
}
func (w *World) RemoveEntity(e Entity) {
	if w == nil {
		return
	}
	w.entityMu.Lock()
	chunkPos, found := w.entities[e]
	if !found {
		w.entityMu.Unlock()
		return
	}
	w.entityMu.Unlock()
	worldsMu.Lock()
	delete(entityWorlds, e)
	worldsMu.Unlock()
	c, ok := w.chunkFromCache(chunkPos)
	if !ok {
		return
	}
	c.Lock()
	n := make([]Entity, 0, len(c.entities))
	for _, entity := range c.entities {
		if entity != e {
			n = append(n, entity)
			continue
		}
	}
	c.entities = n
	var viewers []Viewer
	if len(c.v) > 0 {
		viewers = make([]Viewer, len(c.v))
		copy(viewers, c.v)
	}
	c.Unlock()
	w.entityMu.Lock()
	delete(w.entities, e)
	w.entityMu.Unlock()
	for _, viewer := range viewers {
		viewer.HideEntity(e)
	}
}
func (w *World) EntitiesWithin(aabb physics.AABB, ignored func(Entity) bool) []Entity {
	if w == nil {
		return nil
	}
	m := make([]Entity, 0, 16)
	minPos, maxPos := chunkPosFromVec3(aabb.Min()), chunkPosFromVec3(aabb.Max())
	for x := minPos[0]; x <= maxPos[0]; x++ {
		for z := minPos[1]; z <= maxPos[1]; z++ {
			c, ok := w.chunkFromCache(ChunkPos{x, z})
			if !ok {
				continue
			}
			c.Lock()
			for _, entity := range c.entities {
				if ignored != nil && ignored(entity) {
					continue
				}
				if aabb.Vec3Within(entity.Position()) {
					m = append(m, entity)
				}
			}
			c.Unlock()
		}
	}
	return m
}
func (w *World) Entities() []Entity {
	if w == nil {
		return nil
	}
	w.entityMu.RLock()
	m := make([]Entity, 0, len(w.entities))
	for e := range w.entities {
		m = append(m, e)
	}
	w.entityMu.RUnlock()
	return m
}
func OfEntity(e Entity) (*World, bool) {
	worldsMu.RLock()
	w, ok := entityWorlds[e]
	worldsMu.RUnlock()
	return w, ok
}