func (w *World) StopTime() {
	w.enableTimeCycle(false)
}
StopTime stops the time in the world. When called, the time will no longer cycle and the world will remain
at the time when StopTime is called. The time may be restarted by calling World.StartTime().
StopTime will not do anything if the time is already stopped.
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)
	}
}