├── example.png ├── dragonfly ├── README.md ├── world │ ├── block_states.nbt │ ├── position.go │ ├── block_aliases.nbt │ ├── aliases.go │ ├── settings.go │ ├── dimension.go │ ├── difficulty.go │ ├── game_mode.go │ └── block_state.go ├── mcdb │ ├── version.go │ ├── cache.go │ ├── keys.go │ ├── level_dat.go │ └── provider.go ├── cube │ ├── range.go │ ├── orientation.go │ ├── axis.go │ ├── direction.go │ ├── face.go │ └── pos.go └── chunk │ ├── heightmap.go │ ├── encode.go │ ├── sub_chunk.go │ ├── palette.go │ ├── chunk.go │ ├── encoding.go │ ├── paletted_storage.go │ ├── decode.go │ └── light.go ├── worldrenderer ├── README.md ├── material_mappings.nbt ├── materials.go ├── world.go ├── colours.go └── renderer.go ├── go.mod ├── README.md ├── main.go └── go.sum /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustTalDevelops/worldcompute/HEAD/example.png -------------------------------------------------------------------------------- /dragonfly/README.md: -------------------------------------------------------------------------------- 1 | # dragonfly 2 | packages modified from dragonfly to allow for better world computation support. -------------------------------------------------------------------------------- /dragonfly/world/block_states.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustTalDevelops/worldcompute/HEAD/dragonfly/world/block_states.nbt -------------------------------------------------------------------------------- /worldrenderer/README.md: -------------------------------------------------------------------------------- 1 | # worldrenderer 2 | specialized module of worldcompute to allow for live rendering of minecraft: bedrock worlds -------------------------------------------------------------------------------- /worldrenderer/material_mappings.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JustTalDevelops/worldcompute/HEAD/worldrenderer/material_mappings.nbt -------------------------------------------------------------------------------- /dragonfly/mcdb/version.go: -------------------------------------------------------------------------------- 1 | package mcdb 2 | 3 | import ( 4 | "github.com/sandertv/gophertunnel/minecraft/protocol" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // minimumCompatibleClientVersion is the minimum compatible client version, required by the latest Minecraft data provider. 10 | var minimumCompatibleClientVersion []int32 11 | 12 | // init initializes the minimum compatible client version. 13 | func init() { 14 | fullVersion := append(strings.Split(protocol.CurrentVersion, "."), "0", "0") 15 | for _, v := range fullVersion { 16 | i, _ := strconv.Atoi(v) 17 | minimumCompatibleClientVersion = append(minimumCompatibleClientVersion, int32(i)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dragonfly/cube/range.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | // Range represents the height range of a Dimension in blocks. The first value of the Range holds the minimum Y value, 4 | // the second value holds the maximum Y value. 5 | type Range [2]int 6 | 7 | // Min returns the minimum Y value of a Range. It is equivalent to Range[0]. 8 | func (r Range) Min() int { 9 | return r[0] 10 | } 11 | 12 | // Max returns the maximum Y value of a Range. It is equivalent to Range[1]. 13 | func (r Range) Max() int { 14 | return r[1] 15 | } 16 | 17 | // Height returns the total height of the Range, the difference between Max and Min. 18 | func (r Range) Height() int { 19 | return r[1] - r[0] 20 | } 21 | -------------------------------------------------------------------------------- /dragonfly/world/position.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | // ChunkPos holds the position of a chunk. The type is provided as a utility struct for keeping track of a 4 | // chunk's position. Chunks do not themselves keep track of that. Chunk positions are different from block 5 | // positions in the way that increasing the X/Z by one means increasing the absolute value on the X/Z axis in 6 | // terms of blocks by 16. 7 | type ChunkPos [2]int32 8 | 9 | // X returns the X coordinate of the chunk position. 10 | func (p ChunkPos) X() int32 { 11 | return p[0] 12 | } 13 | 14 | // Z returns the Z coordinate of the chunk position. 15 | func (p ChunkPos) Z() int32 { 16 | return p[1] 17 | } 18 | -------------------------------------------------------------------------------- /dragonfly/world/block_aliases.nbt: -------------------------------------------------------------------------------- 1 | 2 | minecraft:stone_slab2minecraft:stone_block_slab2minecraft:stone_slab3minecraft:stone_block_slab3minecraft:double_stone_slab3"minecraft:double_stone_block_slab3minecraft:movingBlockminecraft:moving_blockminecraft:concretePowderminecraft:concrete_powderminecraft:double_stone_slab!minecraft:double_stone_block_slabminecraft:double_stone_slab4"minecraft:double_stone_block_slab4minecraft:tripWireminecraft:trip_wireminecraft:frog_eggminecraft:frog_spawnminecraft:invisibleBedrockminecraft:invisible_bedrockminecraft:stone_slab4minecraft:stone_block_slab4minecraft:double_stone_slab2"minecraft:double_stone_block_slab2"minecraft:stickyPistonArmCollision%minecraft:sticky_piston_arm_collisionminecraft:seaLanternminecraft:sea_lanternminecraft:pistonArmCollisionminecraft:piston_arm_collisionminecraft:stone_slabminecraft:stone_block_slab -------------------------------------------------------------------------------- /worldrenderer/materials.go: -------------------------------------------------------------------------------- 1 | package worldrenderer 2 | 3 | import ( 4 | _ "embed" 5 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 6 | "github.com/sandertv/gophertunnel/minecraft/nbt" 7 | ) 8 | 9 | var ( 10 | // materials is a map of block runtime ID to material. 11 | materials = make(map[uint32]byte) 12 | //go:embed material_mappings.nbt 13 | mappings []byte 14 | ) 15 | 16 | // init initializes the stateToMaterial map. 17 | func init() { 18 | var m [][]struct { 19 | Name string `nbt:"name"` 20 | Properties map[string]interface{} `nbt:"properties"` 21 | } 22 | if err := nbt.Unmarshal(mappings, &m); err != nil { 23 | panic(err) 24 | } 25 | for id, states := range m { 26 | for _, s := range states { 27 | rid, ok := chunk.StateToRuntimeID(s.Name, s.Properties) 28 | if ok { 29 | materials[rid] = byte(id) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dragonfly/world/aliases.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | _ "embed" 5 | "github.com/sandertv/gophertunnel/minecraft/nbt" 6 | ) 7 | 8 | var ( 9 | //go:embed block_aliases.nbt 10 | blockAliasesData []byte 11 | // aliasMappings maps from a legacy block name alias to an updated name. 12 | aliasMappings = make(map[string]string) 13 | ) 14 | 15 | // upgradeAliasEntry upgrades a possible alias block entry to the correct/updated block entry. 16 | func upgradeAliasEntry(entry blockState) (blockState, bool) { 17 | if alias, ok := aliasMappings[entry.Name]; ok { 18 | entry.Name = alias 19 | return entry, true 20 | } 21 | if entry.Name == "minecraft:barrier" { 22 | entry.Name = "minecraft:info_update" 23 | } 24 | return blockState{}, false 25 | } 26 | 27 | // init creates conversions for each legacy and alias entry. 28 | func init() { 29 | if err := nbt.Unmarshal(blockAliasesData, &aliasMappings); err != nil { 30 | panic(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dragonfly/cube/orientation.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | import "math" 4 | 5 | // Orientation represents the orientation of a sign 6 | type Orientation int 7 | 8 | // OrientationFromYaw returns an Orientation value that (roughly) matches the yaw passed. 9 | func OrientationFromYaw(yaw float64) Orientation { 10 | yaw = math.Mod(yaw, 360) 11 | return Orientation(math.Round(yaw / 360 * 16)) 12 | } 13 | 14 | // Yaw returns the yaw value that matches the orientation. 15 | func (o Orientation) Yaw() float64 { 16 | return float64(o) / 16 * 360 17 | } 18 | 19 | // Opposite returns the opposite orientation value of the Orientation. 20 | func (o Orientation) Opposite() Orientation { 21 | return OrientationFromYaw(o.Yaw() + 180) 22 | } 23 | 24 | // RotateLeft rotates the orientation left by 90 degrees and returns it. 25 | func (o Orientation) RotateLeft() Orientation { 26 | return OrientationFromYaw(o.Yaw() - 90) 27 | } 28 | 29 | // RotateRight rotates the orientation right by 90 degrees and returns it. 30 | func (o Orientation) RotateRight() Orientation { 31 | return OrientationFromYaw(o.Yaw() + 90) 32 | } 33 | -------------------------------------------------------------------------------- /dragonfly/mcdb/cache.go: -------------------------------------------------------------------------------- 1 | package mcdb 2 | 3 | import ( 4 | "github.com/df-mc/goleveldb/leveldb" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | // cache holds a map of path => *leveldb.DB. It is used to cache providers that are re-used for different dimensions 10 | // with the same underlying levelDB database. 11 | cache sync.Map 12 | // refc is a map that serves as reference counter for the *leveldb.DB instances stored in the cache variable above. A 13 | // *leveldb.DB instance is removed from the cache is the ref counter reaches 0. 14 | refc sync.Map 15 | ) 16 | 17 | func cacheLoad(k string) (*leveldb.DB, bool) { 18 | if v, ok := refc.Load(k); ok { 19 | refc.Store(k, v.(int)+1) 20 | db, _ := cache.Load(k) 21 | return db.(*leveldb.DB), true 22 | } 23 | return nil, false 24 | } 25 | 26 | func cacheStore(k string, db *leveldb.DB) { 27 | cache.Store(k, db) 28 | if v, ok := refc.LoadOrStore(k, 1); ok { 29 | refc.Store(k, v.(int)+1) 30 | } 31 | } 32 | 33 | func cacheDelete(k string) int { 34 | v, _ := refc.Load(k) 35 | if v == 1 { 36 | refc.Delete(k) 37 | return 0 38 | } 39 | refc.Store(k, v.(int)-1) 40 | return v.(int) - 1 41 | } 42 | -------------------------------------------------------------------------------- /dragonfly/chunk/heightmap.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | // heightmap represents the heightmap of a chunk. It holds the y value of all the highest blocks in the chunk 4 | // that diffuse or obstruct light. 5 | type heightmap []int16 6 | 7 | // calculateHeightmap calculates the heightmap of the chunk passed and returns it. 8 | func calculateHeightmap(c *Chunk) heightmap { 9 | h := make(heightmap, 256) 10 | 11 | highestY := int16(c.r[0]) 12 | for index := int16(0); index <= int16(len(c.sub)-1); index++ { 13 | if !c.sub[index].Empty() { 14 | highestY = c.SubY(index) + 15 15 | } 16 | } 17 | if highestY == int16(c.r[0]) { 18 | // No non-nil sub chunks present at all. 19 | return h 20 | } 21 | 22 | for x := uint8(0); x < 16; x++ { 23 | for z := uint8(0); z < 16; z++ { 24 | for y := highestY; y >= int16(c.r[0]); y-- { 25 | if filterLevel(c.SubChunk(y), x, uint8(y)&0xf, z) > 0 { 26 | h.set(x, z, y) 27 | break 28 | } 29 | } 30 | } 31 | } 32 | return h 33 | } 34 | 35 | // at returns the heightmap value at a specific column in the chunk. 36 | func (h heightmap) at(x, z uint8) int16 { 37 | return h[(uint16(x)<<4)|uint16(z)] 38 | } 39 | 40 | // set changes the heightmap value at a specific column in the chunk. 41 | func (h heightmap) set(x, z uint8, val int16) { 42 | h[(uint16(x)<<4)|uint16(z)] = val 43 | } 44 | -------------------------------------------------------------------------------- /dragonfly/cube/axis.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Axis represents the axis that a block may be directed in. Most blocks do not have an axis, but blocks such 8 | // as logs or pillars do. 9 | type Axis int 10 | 11 | const ( 12 | // Y represents the vertical Y axis. 13 | Y Axis = iota 14 | // Z represents the horizontal Z axis. 15 | Z 16 | // X represents the horizontal X axis. 17 | X 18 | ) 19 | 20 | // String converts an Axis into either x, y or z, depending on which axis it is. 21 | func (a Axis) String() string { 22 | if a == X { 23 | return "x" 24 | } else if a == Y { 25 | return "y" 26 | } 27 | return "z" 28 | } 29 | 30 | // FromString returns an axis by a string. 31 | func (a Axis) FromString(s string) (interface{}, error) { 32 | switch s { 33 | case "x": 34 | return X, nil 35 | case "y": 36 | return Y, nil 37 | case "z": 38 | return Z, nil 39 | } 40 | return nil, fmt.Errorf("unexpected axis '%v', expecting one of 'x', 'y' or 'z'", s) 41 | } 42 | 43 | // RotateLeft rotates an Axis from X to Z or from Z to X. 44 | func (a Axis) RotateLeft() Axis { 45 | if a == X { 46 | return Z 47 | } else if a == Z { 48 | return X 49 | } 50 | return 0 51 | } 52 | 53 | // RotateRight rotates an Axis from X to Z or from Z to X. 54 | func (a Axis) RotateRight() Axis { 55 | // No difference in rotating left or right for an Axis. 56 | return a.RotateLeft() 57 | } 58 | 59 | // Axes return all possible axes. (x, y, z) 60 | func Axes() []Axis { 61 | return []Axis{X, Y, Z} 62 | } 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/justtaldevelops/worldcompute 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/df-mc/goleveldb v1.1.9 7 | github.com/go-gl/mathgl v1.0.0 8 | github.com/golang/protobuf v1.5.2 // indirect 9 | github.com/golang/snappy v0.0.3 // indirect 10 | github.com/google/uuid v1.3.0 // indirect 11 | github.com/hajimehoshi/ebiten/v2 v2.2.4 12 | github.com/klauspost/compress v1.15.1 // indirect 13 | github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect 14 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 15 | github.com/pelletier/go-toml v1.9.4 16 | github.com/sandertv/go-raknet v1.11.1 // indirect 17 | github.com/sandertv/gophertunnel v1.24.6 18 | go.uber.org/atomic v1.9.0 19 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect 20 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect 21 | golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect 22 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 23 | golang.org/x/text v0.3.7 // indirect 24 | google.golang.org/appengine v1.6.7 // indirect 25 | google.golang.org/protobuf v1.27.1 // indirect 26 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect 27 | ) 28 | 29 | require github.com/sirupsen/logrus v1.9.0 30 | 31 | require ( 32 | github.com/df-mc/atomic v1.10.0 // indirect 33 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be // indirect 34 | github.com/google/go-cmp v0.5.7 // indirect 35 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect 36 | golang.org/x/exp/shiny v0.0.0-20220318154914-8dddf5d87bd8 // indirect 37 | golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5 // indirect 38 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 39 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 40 | gopkg.in/yaml.v2 v2.3.0 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # worldcompute 2 | 3 | live-computation, parsing, and saves of minecraft: bedrock servers using gophertunnel and dragonfly. 4 | 5 | ![example of worldcompute on the hive](example.png) 6 | 7 | ## usage 8 | 9 | download the latest release and move it to its own folder. run the executable and authenticate with xbox live. 10 | 11 | when completed, worldcompute will save your xbox live token in the same folder under `token.tok`. unless you delete the 12 | file, you'll be able to use worldcompute without authenticating again. 13 | 14 | after authenticating, you can close worldcompute. you'll notice that a configuration will be generated. edit the 15 | configuration to your liking, and then run worldcompute. the worldcompute proxy will now forward connections to the 16 | target server specified. 17 | 18 | worldrenderer will automatically run and render the chunks in cache in real-time. 19 | 20 | ## commands 21 | 22 | - `reset` - reset all downloaded chunks in cache. 23 | - `save` - save all downloaded chunks to a folder. 24 | - `cancel` - terminate a save-in-progress. 25 | 26 | ## worldrenderer 27 | 28 | worldrenderer will automatically move based on the position of the player in game. you can also use the following controls 29 | to manage the renderer when you aren't moving in-game: 30 | 31 | - `up` to move the camera up. 32 | - `down` to move the camera down. 33 | - `left` to move the camera to the left. 34 | - `right` to move the camera to the right. 35 | - `scroll up` to scale the rendered world up. 36 | - `scroll down` to scale the rendered world down. 37 | 38 | ## supported formats 39 | 40 | - `v0` (pre-v1.2.13) (legacy, only used by PM3) 41 | - `v1` (post-v1.2.13, only a single layer) 42 | - `v8/v9` (post-v1.2.13, up to 256 layers) (persistent and runtime) 43 | - `v9 (sub-chunk request system)` (post-v1.18.0, up to 256 layers) (persistent and runtime) 44 | -------------------------------------------------------------------------------- /dragonfly/cube/direction.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | // Direction represents a direction towards one of the horizontal axes of the world. 4 | type Direction int 5 | 6 | const ( 7 | // North represents the north direction. 8 | North Direction = iota 9 | // South represents the south direction. 10 | South 11 | // West represents the west direction. 12 | West 13 | // East represents the east direction. 14 | East 15 | ) 16 | 17 | // Opposite returns the opposite direction. 18 | func (d Direction) Opposite() Direction { 19 | switch d { 20 | case North: 21 | return South 22 | case South: 23 | return North 24 | case West: 25 | return East 26 | case East: 27 | return West 28 | } 29 | panic("invalid direction") 30 | } 31 | 32 | // Face converts the direction to a block face. 33 | func (d Direction) Face() Face { 34 | return Face(d + 2) 35 | } 36 | 37 | // RotateRight rotates the direction 90 degrees to the right horizontally and returns the new direction. 38 | func (d Direction) RotateRight() Direction { 39 | switch d { 40 | case North: 41 | return East 42 | case East: 43 | return South 44 | case South: 45 | return West 46 | case West: 47 | return North 48 | } 49 | panic("invalid direction") 50 | } 51 | 52 | // RotateLeft rotates the direction 90 degrees to the left horizontally and returns the new direction. 53 | func (d Direction) RotateLeft() Direction { 54 | switch d { 55 | case North: 56 | return West 57 | case East: 58 | return North 59 | case South: 60 | return East 61 | case West: 62 | return South 63 | } 64 | panic("invalid direction") 65 | } 66 | 67 | // String returns the Direction as a string. 68 | func (d Direction) String() string { 69 | switch d { 70 | case North: 71 | return "north" 72 | case East: 73 | return "east" 74 | case South: 75 | return "south" 76 | case West: 77 | return "west" 78 | } 79 | panic("invalid direction") 80 | } 81 | 82 | var directions = [...]Direction{North, East, South, West} 83 | 84 | // Directions returns a list of all directions, going from North to West. 85 | func Directions() []Direction { 86 | return directions[:] 87 | } 88 | -------------------------------------------------------------------------------- /dragonfly/mcdb/keys.go: -------------------------------------------------------------------------------- 1 | package mcdb 2 | 3 | //lint:file-ignore U1000 Unused unexported constants are present for future code using these. 4 | 5 | // Keys on a per-sub chunk basis. These are prefixed by the chunk coordinates and subchunk ID. 6 | const ( 7 | keySubChunkData = '/' // 2f 8 | ) 9 | 10 | // Keys on a per-chunk basis. These are prefixed by only the chunk coordinates. 11 | const ( 12 | // keyVersion holds a single byte of data with the version of the chunk. 13 | keyVersion = ',' // 2c 14 | // keyVersionOld was replaced by keyVersion. It is still used by vanilla to check compatibility, but vanilla no 15 | // longer writes this tag. 16 | keyVersionOld = 'v' // 76 17 | // keyBlockEntities holds n amount of NBT compound tags appended to each other (not a TAG_List, just appended). The 18 | // compound tags contain the position of the block entities. 19 | keyBlockEntities = '1' // 31 20 | // keyEntities holds n amount of NBT compound tags appended to each other (not a TAG_List, just appended). The 21 | // compound tags contain the position of the entities. 22 | keyEntities = '2' // 32 23 | // keyFinalisation contains a single LE int32 that indicates the state of generation of the chunk. If 0, the chunk 24 | // needs to be ticked. If 1, the chunk needs to be populated and if 2 (which is the state generally found in world 25 | // saves from vanilla), the chunk is fully finalised. 26 | keyFinalisation = '6' // 36 27 | // key3DData holds 3-dimensional biomes for the entire chunk. 28 | key3DData = '+' // 2b 29 | // key2DData is no longer used in worlds with world height change. It was replaced by key3DData in newer worlds 30 | // which has 3-dimensional biomes. 31 | key2DData = '-' // 2d 32 | // keyChecksum holds a list of checksums of some sort. It's not clear of what data this checksum is composed or what 33 | // these checksums are used for. 34 | keyChecksums = ';' // 3b 35 | ) 36 | 37 | // Keys on a per-world basis. These are found only once in a leveldb world save. 38 | const ( 39 | keyAutonomousEntities = "AutonomousEntities" 40 | keyOverworld = "Overworld" 41 | keyMobEvents = "mobevents" 42 | keyBiomeData = "BiomeData" 43 | keyScoreboard = "scoreboard" 44 | keyLocalPlayer = "~local_player" 45 | ) 46 | -------------------------------------------------------------------------------- /dragonfly/world/settings.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 5 | "go.uber.org/atomic" 6 | "sync" 7 | ) 8 | 9 | // Settings holds the settings of a World. These are typically saved to a level.dat file. It is safe to pass the same 10 | // Settings to multiple worlds created using New, in which case the Settings are synchronised between the worlds. 11 | type Settings struct { 12 | sync.Mutex 13 | ref atomic.Int32 14 | 15 | // Name is the display name of the World. 16 | Name string 17 | // Spawn is the spawn position of the World. New players that join the world will be spawned here. 18 | Spawn cube.Pos 19 | // Time is the current time of the World. It advances every tick if TimeCycle is set to true. 20 | Time int64 21 | // TimeCycle specifies if the time should advance every tick. If set to false, time won't change. 22 | TimeCycle bool 23 | // RainTime is the current rain time of the World. It advances every tick if WeatherCycle is set to true. 24 | RainTime int64 25 | // Raining is the current rain level of the World. 26 | Raining bool 27 | // ThunderTime is the current thunder time of the World. It advances every tick if WeatherCycle is set to true. 28 | ThunderTime int64 29 | // Thunder is the current thunder level of the World. 30 | Thundering bool 31 | // WeatherCycle specifies if weather should be enabled in this world. If set to false, weather will be disabled. 32 | WeatherCycle bool 33 | // CurrentTick is the current tick of the world. This is similar to the Time, except that it has no visible effect 34 | // to the client. It can also not be changed through commands and will only ever go up. 35 | CurrentTick int64 36 | // DefaultGameMode is the GameMode assigned to players that join the World for the first time. 37 | DefaultGameMode GameMode 38 | // Difficulty is the difficulty of the World. Behaviour of hunger, regeneration and monsters differs based on the 39 | // difficulty of the world. 40 | Difficulty Difficulty 41 | // TickRange is the radius in chunks around a Viewer that has its blocks and entities ticked when the world is 42 | // ticked. If set to 0, blocks and entities will never be ticked. 43 | TickRange int32 44 | } 45 | 46 | // defaultSettings returns the default Settings for a new World. 47 | func defaultSettings() *Settings { 48 | return &Settings{ 49 | Name: "World", 50 | DefaultGameMode: GameModeSurvival, 51 | Difficulty: DifficultyNormal, 52 | TimeCycle: true, 53 | WeatherCycle: true, 54 | TickRange: 6, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dragonfly/world/dimension.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 5 | "time" 6 | ) 7 | 8 | var ( 9 | // Overworld is the Dimension implementation of a normal overworld. It has a blue sky under normal circumstances and 10 | // has a sun, clouds, stars and a moon. Overworld has a building range of [-64, 320]. 11 | Overworld overworld 12 | // Nether is a Dimension implementation with a lower base light level and a darker sky without sun/moon. It has a 13 | // building range of [0, 256]. 14 | Nether nether 15 | // End is a Dimension implementation with a dark sky. It has a building range of [0, 256]. 16 | End end 17 | ) 18 | 19 | type ( 20 | // Dimension is a dimension of a World. It influences a variety of properties of a World such as the building range, 21 | // the sky colour and the behaviour of liquid blocks. 22 | Dimension interface { 23 | Range() cube.Range 24 | EncodeDimension() int 25 | WaterEvaporates() bool 26 | LavaSpreadDuration() time.Duration 27 | WeatherCycle() bool 28 | TimeCycle() bool 29 | } 30 | overworld struct{} 31 | nether struct{} 32 | end struct{} 33 | ) 34 | 35 | func (overworld) Range() cube.Range { return cube.Range{-64, 319} } 36 | func (overworld) EncodeDimension() int { return 0 } 37 | func (overworld) WaterEvaporates() bool { return false } 38 | func (overworld) LavaSpreadDuration() time.Duration { return time.Second * 3 / 2 } 39 | func (overworld) WeatherCycle() bool { return true } 40 | func (overworld) TimeCycle() bool { return true } 41 | func (overworld) String() string { return "Overworld" } 42 | 43 | func (nether) Range() cube.Range { return cube.Range{0, 127} } 44 | func (nether) EncodeDimension() int { return 1 } 45 | func (nether) WaterEvaporates() bool { return true } 46 | func (nether) LavaSpreadDuration() time.Duration { return time.Second / 4 } 47 | func (nether) WeatherCycle() bool { return false } 48 | func (nether) TimeCycle() bool { return false } 49 | func (nether) String() string { return "Nether" } 50 | 51 | func (end) Range() cube.Range { return cube.Range{0, 255} } 52 | func (end) EncodeDimension() int { return 2 } 53 | func (end) WaterEvaporates() bool { return false } 54 | func (end) LavaSpreadDuration() time.Duration { return time.Second * 3 / 2 } 55 | func (end) WeatherCycle() bool { return false } 56 | func (end) TimeCycle() bool { return false } 57 | func (end) String() string { return "End" } 58 | -------------------------------------------------------------------------------- /worldrenderer/world.go: -------------------------------------------------------------------------------- 1 | package worldrenderer 2 | 3 | import ( 4 | "github.com/hajimehoshi/ebiten/v2" 5 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 6 | "github.com/justtaldevelops/worldcompute/dragonfly/world" 7 | "github.com/nfnt/resize" 8 | "image" 9 | "sync" 10 | ) 11 | 12 | // renderWorld renders a world to *ebiten.Images. 13 | func renderWorld(scale int, chunkMu *sync.Mutex, chunks map[world.ChunkPos]*chunk.Chunk) map[world.ChunkPos]*ebiten.Image { 14 | chunkMu.Lock() 15 | defer chunkMu.Unlock() 16 | 17 | var positions []world.ChunkPos 18 | for pos := range chunks { 19 | positions = append(positions, pos) 20 | } 21 | 22 | rendered := make(map[world.ChunkPos]*ebiten.Image) 23 | for _, pos := range positions { 24 | rendered[pos] = renderChunk(scale, pos, chunks) 25 | } 26 | return rendered 27 | } 28 | 29 | // renderChunk renders a new chunk image from the given chunk. 30 | func renderChunk(scale int, pos world.ChunkPos, chunks map[world.ChunkPos]*chunk.Chunk) *ebiten.Image { 31 | img := image.NewRGBA(image.Rectangle{Max: image.Point{X: 16, Y: 16}}) 32 | ch := chunks[pos] 33 | for x := byte(0); x < 16; x++ { 34 | for z := byte(0); z < 16; z++ { 35 | y := ch.HighestBlock(x, z) 36 | name, properties, _ := chunk.RuntimeIDToState(ch.Block(x, y, z, 0)) 37 | rid, ok := chunk.StateToRuntimeID(name, properties) 38 | if ok { 39 | material := materials[rid] 40 | 41 | northTargetX, northTargetZ := x, z-1 42 | northWestTargetX, northWestTargetZ := x-1, z-1 43 | 44 | northChunk, northExists := chunks[world.ChunkPos{ 45 | int32(northTargetX)>>4 + pos.X(), 46 | int32(northTargetZ)>>4 + pos.Z(), 47 | }] 48 | northWestChunk, northWestExists := chunks[world.ChunkPos{ 49 | int32(northWestTargetX)>>4 + pos.X(), 50 | int32(northWestTargetZ)>>4 + pos.Z(), 51 | }] 52 | 53 | modifier := 0.8627 54 | if northExists && northWestExists { 55 | northY := northChunk.HighestBlock(northTargetX, northTargetZ) 56 | northWestY := northWestChunk.HighestBlock(northWestTargetX, northWestTargetZ) 57 | if northY > y && northWestY <= y { 58 | modifier = 0.7058 59 | } else if northY > y && northWestY > y { 60 | modifier = 0.5294 61 | } else if northY < y && northWestY < y { 62 | modifier = 1 63 | } 64 | } 65 | 66 | colour := materialColours[material] 67 | if material > 0 { 68 | colour.R = uint8(float64(colour.R) * modifier) 69 | colour.G = uint8(float64(colour.G) * modifier) 70 | colour.B = uint8(float64(colour.B) * modifier) 71 | } 72 | 73 | img.Set(int(x), int(z), colour) 74 | } 75 | } 76 | } 77 | return ebiten.NewImageFromImage(resize.Resize(uint(scale*16), uint(scale*16), img, resize.NearestNeighbor)) 78 | } 79 | -------------------------------------------------------------------------------- /worldrenderer/colours.go: -------------------------------------------------------------------------------- 1 | package worldrenderer 2 | 3 | import ( 4 | "image/color" 5 | ) 6 | 7 | // materialColours maps a material ID to a color.RGBA. 8 | var materialColours = map[byte]color.RGBA{ 9 | 0: {R: 221, G: 221, B: 221, A: 255}, 10 | 1: {R: 127, G: 178, B: 56, A: 255}, 11 | 2: {R: 247, G: 233, B: 163, A: 255}, 12 | 3: {R: 199, G: 199, B: 199, A: 255}, 13 | 4: {R: 255, G: 0, B: 0, A: 255}, 14 | 5: {R: 160, G: 160, B: 255, A: 255}, 15 | 6: {R: 167, G: 167, B: 167, A: 255}, 16 | 7: {R: 0, G: 124, B: 0, A: 255}, 17 | 8: {R: 255, G: 255, B: 255, A: 255}, 18 | 9: {R: 164, G: 168, B: 184, A: 255}, 19 | 10: {R: 151, G: 109, B: 77, A: 255}, 20 | 11: {R: 112, G: 112, B: 112, A: 255}, 21 | 12: {R: 64, G: 64, B: 255, A: 255}, 22 | 13: {R: 143, G: 119, B: 72, A: 255}, 23 | 14: {R: 255, G: 252, B: 245, A: 255}, 24 | 15: {R: 216, G: 127, B: 51, A: 255}, 25 | 16: {R: 178, G: 76, B: 216, A: 255}, 26 | 17: {R: 102, G: 153, B: 216, A: 255}, 27 | 18: {R: 229, G: 229, B: 51, A: 255}, 28 | 19: {R: 127, G: 204, B: 25, A: 255}, 29 | 20: {R: 242, G: 127, B: 165, A: 255}, 30 | 21: {R: 76, G: 76, B: 76, A: 255}, 31 | 22: {R: 153, G: 153, B: 153, A: 255}, 32 | 23: {R: 76, G: 127, B: 153, A: 255}, 33 | 24: {R: 127, G: 63, B: 178, A: 255}, 34 | 25: {R: 51, G: 76, B: 178, A: 255}, 35 | 26: {R: 102, G: 76, B: 51, A: 255}, 36 | 27: {R: 102, G: 127, B: 51, A: 255}, 37 | 28: {R: 153, G: 51, B: 51, A: 255}, 38 | 29: {R: 25, G: 25, B: 25, A: 255}, 39 | 30: {R: 250, G: 238, B: 77, A: 255}, 40 | 31: {R: 92, G: 219, B: 213, A: 255}, 41 | 32: {R: 74, G: 128, B: 255, A: 255}, 42 | 33: {R: 0, G: 217, B: 58, A: 255}, 43 | 34: {R: 129, G: 86, B: 49, A: 255}, 44 | 35: {R: 112, G: 2, B: 0, A: 255}, 45 | 36: {R: 209, G: 177, B: 161, A: 255}, 46 | 37: {R: 159, G: 82, B: 36, A: 255}, 47 | 38: {R: 149, G: 87, B: 108, A: 255}, 48 | 39: {R: 112, G: 108, B: 138, A: 255}, 49 | 40: {R: 186, G: 133, B: 36, A: 255}, 50 | 41: {R: 103, G: 117, B: 53, A: 255}, 51 | 42: {R: 160, G: 77, B: 78, A: 255}, 52 | 43: {R: 57, G: 41, B: 35, A: 255}, 53 | 44: {R: 135, G: 107, B: 98, A: 255}, 54 | 45: {R: 87, G: 92, B: 92, A: 255}, 55 | 46: {R: 122, G: 73, B: 88, A: 255}, 56 | 47: {R: 76, G: 62, B: 92, A: 255}, 57 | 48: {R: 76, G: 50, B: 35, A: 255}, 58 | 49: {R: 76, G: 82, B: 42, A: 255}, 59 | 50: {R: 142, G: 60, B: 46, A: 255}, 60 | 51: {R: 37, G: 22, B: 16, A: 255}, 61 | 52: {R: 189, G: 48, B: 49, A: 255}, 62 | 53: {R: 148, G: 63, B: 97, A: 255}, 63 | 54: {R: 92, G: 25, B: 29, A: 255}, 64 | 55: {R: 22, G: 126, B: 134, A: 255}, 65 | 56: {R: 58, G: 142, B: 140, A: 255}, 66 | 57: {R: 86, G: 44, B: 62, A: 255}, 67 | 58: {R: 20, G: 180, B: 133, A: 255}, 68 | 59: {R: 100, G: 100, B: 100, A: 255}, 69 | 60: {R: 216, G: 175, B: 147, A: 255}, 70 | 61: {R: 127, G: 167, B: 150, A: 255}, 71 | } 72 | -------------------------------------------------------------------------------- /dragonfly/world/difficulty.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | // Difficulty represents the difficulty of a Minecraft world. The difficulty of a world influences all kinds 4 | // of aspects of the world, such as the damage enemies deal to players, the way hunger depletes, whether 5 | // hostile monsters spawn or not and more. 6 | type Difficulty interface { 7 | // FoodRegenerates specifies if players' food levels should automatically regenerate with this difficulty. 8 | FoodRegenerates() bool 9 | // StarvationHealthLimit specifies the amount of health at which a player will no longer receive damage from 10 | // starvation. 11 | StarvationHealthLimit() float64 12 | // FireSpreadIncrease returns a number that increases the rate at which fire spreads. 13 | FireSpreadIncrease() int 14 | } 15 | 16 | var ( 17 | // DifficultyPeaceful prevents most hostile mobs from spawning and makes players rapidly regenerate health and food. 18 | DifficultyPeaceful difficultyPeaceful 19 | // DifficultyEasy has mobs that deal less damage to players than normal and starvation won't occur if a player has 20 | // less than 5 hearts of health. 21 | DifficultyEasy difficultyEasy 22 | // DifficultyNormal has mobs that deal normal damage to players. Starvation will occur until the player is down to 23 | // a single heart. 24 | DifficultyNormal difficultyNormal 25 | // DifficultyHard has mobs that deal above average damage to players. Starvation will kill players with too little 26 | // food and monsters will get additional effects. 27 | DifficultyHard difficultyHard 28 | ) 29 | 30 | // difficultyPeaceful difficulty prevents most hostile mobs from spawning and makes players rapidly regenerate 31 | // health and food. 32 | type difficultyPeaceful struct{} 33 | 34 | func (difficultyPeaceful) FoodRegenerates() bool { return true } 35 | func (difficultyPeaceful) StarvationHealthLimit() float64 { return 20 } 36 | func (difficultyPeaceful) FireSpreadIncrease() int { return 0 } 37 | 38 | // difficultyEasy difficulty has mobs deal less damage to players than normal and starvation won't occur if 39 | // a player has less than 5 hearts of health. 40 | type difficultyEasy struct{} 41 | 42 | func (difficultyEasy) FoodRegenerates() bool { return false } 43 | func (difficultyEasy) StarvationHealthLimit() float64 { return 10 } 44 | func (difficultyEasy) FireSpreadIncrease() int { return 7 } 45 | 46 | // difficultyNormal difficulty has mobs that deal normal damage to players. Starvation will occur until the 47 | // player is down to a single heart. 48 | type difficultyNormal struct{} 49 | 50 | func (difficultyNormal) FoodRegenerates() bool { return false } 51 | func (difficultyNormal) StarvationHealthLimit() float64 { return 2 } 52 | func (difficultyNormal) FireSpreadIncrease() int { return 14 } 53 | 54 | // difficultyHard difficulty has mobs that deal above average damage to players. Starvation will kill players 55 | // with too little food and monsters will get additional effects. 56 | type difficultyHard struct{} 57 | 58 | func (difficultyHard) FoodRegenerates() bool { return false } 59 | func (difficultyHard) StarvationHealthLimit() float64 { return -1 } 60 | func (difficultyHard) FireSpreadIncrease() int { return 21 } 61 | -------------------------------------------------------------------------------- /dragonfly/cube/face.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | const ( 8 | // FaceDown represents the bottom face of a block. 9 | FaceDown Face = iota 10 | // FaceUp represents the top face of a block. 11 | FaceUp 12 | // FaceNorth represents the north face of a block. 13 | FaceNorth 14 | // FaceSouth represents the south face of a block. 15 | FaceSouth 16 | // FaceWest represents the west face of the block. 17 | FaceWest 18 | // FaceEast represents the east face of the block. 19 | FaceEast 20 | ) 21 | 22 | // Face represents the face of a block or entity. 23 | type Face int 24 | 25 | // Direction converts the Face to a Direction and returns it, assuming the Face is horizontal and not FaceUp 26 | // or FaceDown. 27 | func (f Face) Direction() Direction { 28 | return Direction(f - 2) 29 | } 30 | 31 | // FromString returns a Face by a string. 32 | func (f Face) FromString(s string) (interface{}, error) { 33 | switch s { 34 | case "down": 35 | return FaceDown, nil 36 | case "up": 37 | return FaceUp, nil 38 | case "north": 39 | return FaceNorth, nil 40 | case "south": 41 | return FaceSouth, nil 42 | case "west": 43 | return FaceWest, nil 44 | case "east": 45 | return FaceEast, nil 46 | } 47 | return nil, fmt.Errorf("unexpected facing '%v', expecting one of 'down', 'up', 'north', 'south', 'west' or 'east'", s) 48 | } 49 | 50 | // Opposite returns the opposite face. FaceDown will return up, north will return south and west will return east, 51 | // and vice versa. 52 | func (f Face) Opposite() Face { 53 | switch f { 54 | default: 55 | return FaceUp 56 | case FaceUp: 57 | return FaceDown 58 | case FaceNorth: 59 | return FaceSouth 60 | case FaceSouth: 61 | return FaceNorth 62 | case FaceWest: 63 | return FaceEast 64 | case FaceEast: 65 | return FaceWest 66 | } 67 | } 68 | 69 | // Axis returns the axis the face is facing. FaceEast and west correspond to the x-axis, north and south to the z 70 | // axis and up and down to the y-axis. 71 | func (f Face) Axis() Axis { 72 | switch f { 73 | default: 74 | return Y 75 | case FaceEast, FaceWest: 76 | return X 77 | case FaceNorth, FaceSouth: 78 | return Z 79 | } 80 | } 81 | 82 | // RotateRight rotates the face 90 degrees to the right horizontally and returns the new face. 83 | func (f Face) RotateRight() Face { 84 | switch f { 85 | case FaceNorth: 86 | return FaceEast 87 | case FaceEast: 88 | return FaceSouth 89 | case FaceSouth: 90 | return FaceWest 91 | case FaceWest: 92 | return FaceNorth 93 | } 94 | return f 95 | } 96 | 97 | // RotateLeft rotates the face 90 degrees to the left horizontally and returns the new face. 98 | func (f Face) RotateLeft() Face { 99 | switch f { 100 | case FaceNorth: 101 | return FaceWest 102 | case FaceEast: 103 | return FaceNorth 104 | case FaceSouth: 105 | return FaceEast 106 | case FaceWest: 107 | return FaceSouth 108 | } 109 | return f 110 | } 111 | 112 | // String returns the Face as a string. 113 | func (f Face) String() string { 114 | switch f { 115 | case FaceUp: 116 | return "up" 117 | case FaceDown: 118 | return "down" 119 | case FaceNorth: 120 | return "north" 121 | case FaceSouth: 122 | return "south" 123 | case FaceWest: 124 | return "west" 125 | case FaceEast: 126 | return "east" 127 | } 128 | panic("invalid face") 129 | } 130 | 131 | // Faces returns a list of all faces, starting with down, then up, then north to west. 132 | func Faces() []Face { 133 | return faces[:] 134 | } 135 | 136 | // HorizontalFaces returns a list of all horizontal faces, from north to west. 137 | func HorizontalFaces() []Face { 138 | return hFaces[:] 139 | } 140 | 141 | var hFaces = [...]Face{FaceNorth, FaceEast, FaceSouth, FaceWest} 142 | 143 | var faces = [...]Face{FaceDown, FaceUp, FaceNorth, FaceEast, FaceSouth, FaceWest} 144 | -------------------------------------------------------------------------------- /dragonfly/cube/pos.go: -------------------------------------------------------------------------------- 1 | package cube 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-gl/mathgl/mgl64" 6 | "math" 7 | ) 8 | 9 | // Pos holds the position of a block. The position is represented of an array with an x, y and z value, 10 | // where the y value is positive. 11 | type Pos [3]int 12 | 13 | // String converts the Pos to a string in the format (1,2,3) and returns it. 14 | func (p Pos) String() string { 15 | return fmt.Sprintf("(%v,%v,%v)", p[0], p[1], p[2]) 16 | } 17 | 18 | // X returns the X coordinate of the block position. 19 | func (p Pos) X() int { 20 | return p[0] 21 | } 22 | 23 | // Y returns the Y coordinate of the block position. 24 | func (p Pos) Y() int { 25 | return p[1] 26 | } 27 | 28 | // Z returns the Z coordinate of the block position. 29 | func (p Pos) Z() int { 30 | return p[2] 31 | } 32 | 33 | // OutOfBounds checks if the Y value is either bigger than r[1] or smaller than r[0]. 34 | func (p Pos) OutOfBounds(r Range) bool { 35 | y := p[1] 36 | return y > r[1] || y < r[0] 37 | } 38 | 39 | // Add adds two block positions together and returns a new one with the combined values. 40 | func (p Pos) Add(pos Pos) Pos { 41 | return Pos{p[0] + pos[0], p[1] + pos[1], p[2] + pos[2]} 42 | } 43 | 44 | // Subtract subtracts two block positions together and returns a new one with the combined values. 45 | func (p Pos) Subtract(pos Pos) Pos { 46 | return Pos{p[0] - pos[0], p[1] - pos[1], p[2] - pos[2]} 47 | } 48 | 49 | // Vec3 returns a vec3 holding the same coordinates as the block position. 50 | func (p Pos) Vec3() mgl64.Vec3 { 51 | return mgl64.Vec3{float64(p[0]), float64(p[1]), float64(p[2])} 52 | } 53 | 54 | // Vec3Middle returns a Vec3 holding the coordinates of the block position with 0.5 added on both horizontal 55 | // axes. 56 | func (p Pos) Vec3Middle() mgl64.Vec3 { 57 | return mgl64.Vec3{float64(p[0]) + 0.5, float64(p[1]), float64(p[2]) + 0.5} 58 | } 59 | 60 | // Vec3Centre returns a Vec3 holding the coordinates of the block position with 0.5 added on all axes. 61 | func (p Pos) Vec3Centre() mgl64.Vec3 { 62 | return mgl64.Vec3{float64(p[0]) + 0.5, float64(p[1]) + 0.5, float64(p[2]) + 0.5} 63 | } 64 | 65 | // Side returns the position on the side of this block position, at a specific face. 66 | func (p Pos) Side(face Face) Pos { 67 | switch face { 68 | case FaceUp: 69 | p[1]++ 70 | case FaceDown: 71 | p[1]-- 72 | case FaceNorth: 73 | p[2]-- 74 | case FaceSouth: 75 | p[2]++ 76 | case FaceWest: 77 | p[0]-- 78 | case FaceEast: 79 | p[0]++ 80 | } 81 | return p 82 | } 83 | 84 | // Face returns the face that the other Pos was on compared to the current Pos. The other Pos 85 | // is assumed to be a direct neighbour of the current Pos. 86 | func (p Pos) Face(other Pos) Face { 87 | switch other { 88 | case p.Add(Pos{0, 1}): 89 | return FaceUp 90 | case p.Add(Pos{0, -1}): 91 | return FaceDown 92 | case p.Add(Pos{0, 0, -1}): 93 | return FaceNorth 94 | case p.Add(Pos{0, 0, 1}): 95 | return FaceSouth 96 | case p.Add(Pos{-1, 0, 0}): 97 | return FaceWest 98 | case p.Add(Pos{1, 0, 0}): 99 | return FaceEast 100 | } 101 | return FaceUp 102 | } 103 | 104 | // Neighbours calls the function passed for each of the block position's neighbours. If the Y value is out of 105 | // bounds, the function will not be called for that position. 106 | func (p Pos) Neighbours(f func(neighbour Pos), r Range) { 107 | if p.OutOfBounds(r) { 108 | return 109 | } 110 | p[0]++ 111 | f(p) 112 | p[0] -= 2 113 | f(p) 114 | p[0]++ 115 | p[1]++ 116 | if p[1] <= r[1] { 117 | f(p) 118 | } 119 | p[1] -= 2 120 | if p[1] >= r[0] { 121 | f(p) 122 | } 123 | p[1]++ 124 | p[2]++ 125 | f(p) 126 | p[2] -= 2 127 | f(p) 128 | } 129 | 130 | // PosFromVec3 returns a block position by a Vec3, rounding the values down adequately. 131 | func PosFromVec3(vec3 mgl64.Vec3) Pos { 132 | return Pos{int(math.Floor(vec3[0])), int(math.Floor(vec3[1])), int(math.Floor(vec3[2]))} 133 | } 134 | -------------------------------------------------------------------------------- /dragonfly/chunk/encode.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | ) 7 | 8 | const ( 9 | // SubChunkVersion is the current version of the written sub chunks, specifying the format they are 10 | // written on disk and over network. 11 | SubChunkVersion = 9 12 | // CurrentBlockVersion is the current version of blocks (states) of the game. This version is composed 13 | // of 4 bytes indicating a version, interpreted as a big endian int. The current version represents 14 | // 1.16.0.14 {1, 16, 0, 14}. 15 | CurrentBlockVersion int32 = 17825806 16 | ) 17 | 18 | var ( 19 | // RuntimeIDToState must hold a function to convert a runtime ID to a name and its state properties. 20 | RuntimeIDToState func(runtimeID uint32) (name string, properties map[string]interface{}, found bool) 21 | // pool is used to pool byte buffers used for encoding chunks. 22 | pool = sync.Pool{ 23 | New: func() interface{} { 24 | return bytes.NewBuffer(make([]byte, 0, 1024)) 25 | }, 26 | } 27 | ) 28 | 29 | type ( 30 | // SerialisedData holds the serialised data of a chunk. It consists of the chunk's block data itself, a height 31 | // map, the biomes and entities and block entities. 32 | SerialisedData struct { 33 | // sub holds the data of the serialised sub chunks in a chunk. Sub chunks that are empty or that otherwise 34 | // don't exist are represented as an empty slice (or technically, nil). 35 | SubChunks [][]byte 36 | // Biomes is the biome data of the chunk, which is composed of a biome storage for each subchunk. 37 | Biomes []byte 38 | // BlockNBT is an encoded NBT array of all blocks that carry additional NBT, such as chests, with all 39 | // their contents. 40 | BlockNBT []byte 41 | } 42 | // blockEntry represents a block as found in a disk save of a world. 43 | blockEntry struct { 44 | Name string `nbt:"name"` 45 | State map[string]interface{} `nbt:"states"` 46 | Version int32 `nbt:"version"` 47 | } 48 | ) 49 | 50 | // Encode encodes Chunk to an intermediate representation SerialisedData. An Encoding may be passed to encode either for 51 | // network or disk purposed, the most notable difference being that the network encoding generally uses varints and no 52 | // NBT. 53 | func Encode(c *Chunk, e Encoding) SerialisedData { 54 | buf := pool.Get().(*bytes.Buffer) 55 | defer func() { 56 | buf.Reset() 57 | pool.Put(buf) 58 | }() 59 | 60 | d := encodeSubChunks(buf, c, e) 61 | for i := range c.biomes { 62 | encodePalettedStorage(buf, c.biomes[i], e, BiomePaletteEncoding) 63 | } 64 | d.Biomes = append([]byte(nil), buf.Bytes()...) 65 | 66 | return d 67 | } 68 | 69 | // encodeSubChunks encodes the sub chunks of the Chunk passed into the bytes.Buffer buf. It uses the encoding passed to 70 | // encode the block storages and returns the resulting SerialisedData. 71 | func encodeSubChunks(buf *bytes.Buffer, c *Chunk, e Encoding) (d SerialisedData) { 72 | d.SubChunks = make([][]byte, len(c.sub)) 73 | for i, sub := range c.sub { 74 | _, _ = buf.Write([]byte{SubChunkVersion, byte(len(sub.storages)), uint8(i + (c.r[0] >> 4))}) 75 | for _, storage := range sub.storages { 76 | encodePalettedStorage(buf, storage, e, BlockPaletteEncoding) 77 | } 78 | d.SubChunks[i] = make([]byte, buf.Len()) 79 | _, _ = buf.Read(d.SubChunks[i]) 80 | } 81 | return 82 | } 83 | 84 | // encodePalettedStorage encodes a PalettedStorage into a bytes.Buffer. The Encoding passed is used to write the Palette 85 | // of the PalettedStorage. 86 | func encodePalettedStorage(buf *bytes.Buffer, storage *PalettedStorage, e Encoding, pe paletteEncoding) { 87 | b := make([]byte, len(storage.indices)*4+1) 88 | b[0] = byte(storage.bitsPerIndex<<1) | e.network() 89 | 90 | for i, v := range storage.indices { 91 | // Explicitly don't use the binary package to greatly improve performance of writing the uint32s. 92 | b[i*4+1], b[i*4+2], b[i*4+3], b[i*4+4] = byte(v), byte(v>>8), byte(v>>16), byte(v>>24) 93 | } 94 | _, _ = buf.Write(b) 95 | 96 | e.encodePalette(buf, storage.palette, pe) 97 | } 98 | -------------------------------------------------------------------------------- /worldrenderer/renderer.go: -------------------------------------------------------------------------------- 1 | package worldrenderer 2 | 3 | import ( 4 | "github.com/go-gl/mathgl/mgl64" 5 | "github.com/hajimehoshi/ebiten/v2" 6 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 7 | "github.com/justtaldevelops/worldcompute/dragonfly/world" 8 | "sync" 9 | ) 10 | 11 | // Renderer implements the ebiten.Game interface. 12 | type Renderer struct { 13 | scale int 14 | drift float64 15 | pos mgl64.Vec2 16 | 17 | needsRerender bool 18 | shouldCenter bool 19 | centerPos mgl64.Vec2 20 | 21 | chunkMu *sync.Mutex 22 | chunks map[world.ChunkPos]*chunk.Chunk 23 | 24 | renderMu *sync.Mutex 25 | renderCache map[world.ChunkPos]*ebiten.Image 26 | } 27 | 28 | // NewRendererDirect creates a new renderer with the given chunks. 29 | func NewRendererDirect(scale int, drift float64, centerPos mgl64.Vec2, chunkMu *sync.Mutex, chunks map[world.ChunkPos]*chunk.Chunk) *Renderer { 30 | r := &Renderer{scale: scale, drift: drift, renderMu: new(sync.Mutex), shouldCenter: true} 31 | r.renderCache = renderWorld(r.scale, chunkMu, chunks) 32 | r.centerPos = centerPos 33 | r.chunkMu = chunkMu 34 | r.chunks = chunks 35 | return r 36 | } 37 | 38 | // Update proceeds the renderer state. 39 | func (r *Renderer) Update() error { 40 | if ebiten.IsKeyPressed(ebiten.KeyUp) { 41 | r.pos = r.pos.Add(mgl64.Vec2{0, -r.drift}) 42 | } 43 | if ebiten.IsKeyPressed(ebiten.KeyDown) { 44 | r.pos = r.pos.Add(mgl64.Vec2{0, r.drift}) 45 | } 46 | if ebiten.IsKeyPressed(ebiten.KeyLeft) { 47 | r.pos = r.pos.Add(mgl64.Vec2{-r.drift, 0}) 48 | } 49 | if ebiten.IsKeyPressed(ebiten.KeyRight) { 50 | r.pos = r.pos.Add(mgl64.Vec2{r.drift, 0}) 51 | } 52 | 53 | oldScale := r.scale 54 | _, yOff := ebiten.Wheel() 55 | if yOff > 0 { 56 | r.scale++ 57 | } else if yOff < 0 { 58 | r.scale-- 59 | } 60 | if r.scale <= 0 { 61 | r.scale = 1 62 | } 63 | if oldScale != r.scale || len(r.renderCache) != len(r.chunks) { 64 | r.Rerender() 65 | r.pos = r.pos.Mul(float64(r.scale) / (float64(oldScale))) 66 | } 67 | if r.needsRerender { 68 | r.renderCache = renderWorld(r.scale, r.chunkMu, r.chunks) 69 | r.needsRerender = false 70 | } 71 | return nil 72 | } 73 | 74 | // Draw draws the screen. 75 | func (r *Renderer) Draw(screen *ebiten.Image) { 76 | screen.Fill(materialColours[0]) 77 | 78 | w, h := screen.Size() 79 | chunkScale := float64(r.scale) * 16 80 | centerX, centerZ := float64(w/2), float64(h/2) 81 | if r.shouldCenter { 82 | r.pos = r.centerPos.Mul(float64(r.scale)) 83 | r.shouldCenter = false 84 | } 85 | 86 | r.renderMu.Lock() 87 | defer r.renderMu.Unlock() 88 | for pos, ch := range r.renderCache { 89 | chunkW, chunkH := ch.Bounds().Dx(), ch.Bounds().Dy() 90 | offsetX, offsetZ := float64(chunkW/2)+r.pos.X(), float64(chunkH/2)+r.pos.Y() 91 | 92 | chunkX, chunkZ := centerX+(float64(pos.X())*chunkScale), centerZ+(float64(pos.Z())*chunkScale) 93 | 94 | geo := ebiten.GeoM{} 95 | geo.Translate(chunkX-offsetX, chunkZ-offsetZ) 96 | screen.DrawImage(ch, &ebiten.DrawImageOptions{GeoM: geo}) 97 | } 98 | } 99 | 100 | // Layout takes the outside size (e.g., the window size) and returns the (logical) screen size. 101 | func (r *Renderer) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { 102 | return outsideWidth, outsideHeight 103 | } 104 | 105 | // Rerender rerenders the world. 106 | func (r *Renderer) Rerender() { 107 | r.needsRerender = true 108 | } 109 | 110 | // RerenderChunk rerenders the chunk at the given position. 111 | func (r *Renderer) RerenderChunk(pos world.ChunkPos) { 112 | r.chunkMu.Lock() 113 | r.renderMu.Lock() 114 | defer r.chunkMu.Unlock() 115 | defer r.renderMu.Unlock() 116 | 117 | renderPositions := []world.ChunkPos{ 118 | {pos.X(), pos.Z() + 1}, 119 | {pos.X(), pos.Z() - 1}, 120 | {pos.X() + 1, pos.Z()}, 121 | {pos.X() - 1, pos.Z()}, 122 | pos, 123 | } 124 | for _, renderPos := range renderPositions { 125 | if _, ok := r.chunks[renderPos]; !ok { 126 | // Chunk doesn't exist, so we couldn't possibly render it. 127 | delete(r.renderCache, renderPos) 128 | continue 129 | } 130 | r.renderCache[renderPos] = renderChunk(r.scale, renderPos, r.chunks) 131 | } 132 | } 133 | 134 | // Recenter centers the renderer on the given chunk. 135 | func (r *Renderer) Recenter(pos mgl64.Vec2) { 136 | r.centerPos = pos 137 | r.shouldCenter = true 138 | } 139 | -------------------------------------------------------------------------------- /dragonfly/chunk/sub_chunk.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | // SubChunk is a cube of blocks located in a chunk. It has a size of 16x16x16 blocks and forms part of a stack 4 | // that forms a Chunk. 5 | type SubChunk struct { 6 | air uint32 7 | storages []*PalettedStorage 8 | blockLight []uint8 9 | skyLight []uint8 10 | } 11 | 12 | // NewSubChunk creates a new sub chunk. All sub chunks should be created through this function 13 | func NewSubChunk(air uint32) *SubChunk { 14 | return &SubChunk{air: air} 15 | } 16 | 17 | // Empty checks if the SubChunk is considered empty. This is the case if the SubChunk has 0 block storages or if it has 18 | // a single one that is completely filled with air. 19 | func (sub *SubChunk) Empty() bool { 20 | return len(sub.storages) == 0 || (len(sub.storages) == 1 && len(sub.storages[0].palette.values) == 1 && sub.storages[0].palette.values[0] == sub.air) 21 | } 22 | 23 | // Layer returns a certain block storage/layer from a sub chunk. If no storage at the layer exists, the layer 24 | // is created, as well as all layers between the current highest layer and the new highest layer. 25 | func (sub *SubChunk) Layer(layer uint8) *PalettedStorage { 26 | for i := uint8(len(sub.storages)); i <= layer; i++ { 27 | // Keep appending to storages until the requested layer is achieved. Makes working with new layers 28 | // much easier. 29 | sub.storages = append(sub.storages, emptyStorage(sub.air)) 30 | } 31 | return sub.storages[layer] 32 | } 33 | 34 | // Layers returns all layers in the sub chunk. This method may also return an empty slice. 35 | func (sub *SubChunk) Layers() []*PalettedStorage { 36 | return sub.storages 37 | } 38 | 39 | // Block returns the runtime ID of the block located at the given X, Y and Z. X, Y and Z must be in a 40 | // range of 0-15. 41 | func (sub *SubChunk) Block(x, y, z byte, layer uint8) uint32 { 42 | if uint8(len(sub.storages)) <= layer { 43 | return sub.air 44 | } 45 | return sub.storages[layer].At(x, y, z) 46 | } 47 | 48 | // SetBlock sets the given block runtime ID at the given X, Y and Z. X, Y and Z must be in a range of 0-15. 49 | func (sub *SubChunk) SetBlock(x, y, z byte, layer uint8, block uint32) { 50 | sub.Layer(layer).Set(x, y, z, block) 51 | } 52 | 53 | // SetBlockLight sets the block light value at a specific position in the sub chunk. 54 | func (sub *SubChunk) SetBlockLight(x, y, z byte, level uint8) { 55 | if ptr := &sub.blockLight[0]; ptr == noLightPtr { 56 | // Copy the block light as soon as it is changed to create a COW system. 57 | sub.blockLight = append([]byte(nil), sub.blockLight...) 58 | } 59 | index := (uint16(x) << 8) | (uint16(z) << 4) | uint16(y) 60 | 61 | i := index >> 1 62 | bit := (index & 1) << 2 63 | sub.blockLight[i] = (sub.blockLight[i] & (0xf0 >> bit)) | (level << bit) 64 | } 65 | 66 | // BlockLight returns the block light value at a specific value at a specific position in the sub chunk. 67 | func (sub *SubChunk) BlockLight(x, y, z byte) uint8 { 68 | index := (uint16(x) << 8) | (uint16(z) << 4) | uint16(y) 69 | 70 | return (sub.blockLight[index>>1] >> ((index & 1) << 2)) & 0xf 71 | } 72 | 73 | // SetSkyLight sets the skylight value at a specific position in the sub chunk. 74 | func (sub *SubChunk) SetSkyLight(x, y, z byte, level uint8) { 75 | if ptr := &sub.skyLight[0]; ptr == fullLightPtr || ptr == noLightPtr { 76 | // Copy the skylight as soon as it is changed to create a COW system. 77 | sub.skyLight = append([]byte(nil), sub.skyLight...) 78 | } 79 | index := (uint16(x) << 8) | (uint16(z) << 4) | uint16(y) 80 | 81 | i := index >> 1 82 | bit := (index & 1) << 2 83 | sub.skyLight[i] = (sub.skyLight[i] & (0xf0 >> bit)) | (level << bit) 84 | } 85 | 86 | // SkyLight returns the skylight value at a specific value at a specific position in the sub chunk. 87 | func (sub *SubChunk) SkyLight(x, y, z byte) uint8 { 88 | index := (uint16(x) << 8) | (uint16(z) << 4) | uint16(y) 89 | 90 | return (sub.skyLight[index>>1] >> ((index & 1) << 2)) & 0xf 91 | } 92 | 93 | // Compact cleans the garbage from all block storages that sub chunk contains, so that they may be 94 | // cleanly written to a database. 95 | func (sub *SubChunk) compact() { 96 | newStorages := make([]*PalettedStorage, 0, len(sub.storages)) 97 | for _, storage := range sub.storages { 98 | storage.compact() 99 | if len(storage.palette.values) == 1 && storage.palette.values[0] == sub.air { 100 | // If the palette has only air in it, it means the storage is empty, so we can ignore it. 101 | continue 102 | } 103 | newStorages = append(newStorages, storage) 104 | } 105 | sub.storages = newStorages 106 | } 107 | -------------------------------------------------------------------------------- /dragonfly/chunk/palette.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // paletteSize is the size of a palette. It indicates the amount of bits occupied per value stored. 8 | type paletteSize byte 9 | 10 | // Palette is a palette of values that every PalettedStorage has. Storages hold 'pointers' to indices 11 | // in this palette. 12 | type Palette struct { 13 | last uint32 14 | lastIndex int16 15 | size paletteSize 16 | 17 | // values is a map of values. A PalettedStorage points to the index to this value. 18 | values []uint32 19 | } 20 | 21 | // newPalette returns a new Palette with size and a slice of added values. 22 | func newPalette(size paletteSize, values []uint32) *Palette { 23 | return &Palette{size: size, values: values, last: math.MaxUint32} 24 | } 25 | 26 | // Len returns the amount of unique values in the Palette. 27 | func (palette *Palette) Len() int { 28 | return len(palette.values) 29 | } 30 | 31 | // Add adds a values to the Palette. It does not first check if the value was already set in the Palette. 32 | // The index at which the value was added is returned. Another bool is returned indicating if the Palette 33 | // was resized as a result of adding the value. 34 | func (palette *Palette) Add(v uint32) (index int16, resize bool) { 35 | i := int16(len(palette.values)) 36 | palette.values = append(palette.values, v) 37 | 38 | if palette.needsResize() { 39 | palette.increaseSize() 40 | return i, true 41 | } 42 | return i, false 43 | } 44 | 45 | // Replace calls the function passed for each value present in the Palette. The value returned by the 46 | // function replaces the value present at the index of the value passed. 47 | func (palette *Palette) Replace(f func(v uint32) uint32) { 48 | // Reset last runtime ID as it now has a different offset. 49 | palette.last = math.MaxUint32 50 | for index, v := range palette.values { 51 | palette.values[index] = f(v) 52 | } 53 | } 54 | 55 | // Index loops through the values of the Palette and looks for the index of the given value. If the value could 56 | // not be found, -1 is returned. 57 | func (palette *Palette) Index(runtimeID uint32) int16 { 58 | if runtimeID == palette.last { 59 | // Fast path out. 60 | return palette.lastIndex 61 | } 62 | // Slow path in a separate function allows for inlining the fast path. 63 | return palette.indexSlow(runtimeID) 64 | } 65 | 66 | // indexSlow searches the index of a value in the Palette's values by iterating through the Palette's values. 67 | func (palette *Palette) indexSlow(runtimeID uint32) int16 { 68 | l := len(palette.values) 69 | for i := 0; i < l; i++ { 70 | if palette.values[i] == runtimeID { 71 | palette.last = runtimeID 72 | v := int16(i) 73 | palette.lastIndex = v 74 | return v 75 | } 76 | } 77 | return -1 78 | } 79 | 80 | // Value returns the value in the Palette at a specific index. 81 | func (palette *Palette) Value(i uint16) uint32 { 82 | return palette.values[i] 83 | } 84 | 85 | // needsResize checks if the Palette, and with it the holding PalettedStorage, needs to be resized to a bigger 86 | // size. 87 | func (palette *Palette) needsResize() bool { 88 | return len(palette.values) > (1 << palette.size) 89 | } 90 | 91 | var sizes = [...]paletteSize{0, 1, 2, 3, 4, 5, 6, 8, 16} 92 | var offsets = [...]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 8: 7, 16: 8} 93 | 94 | // increaseSize increases the size of the Palette to the next palette size. 95 | func (palette *Palette) increaseSize() { 96 | palette.size = sizes[offsets[palette.size]+1] 97 | } 98 | 99 | // padded returns true if the Palette size is 3, 5 or 6. 100 | func (p paletteSize) padded() bool { 101 | return p == 3 || p == 5 || p == 6 102 | } 103 | 104 | // paletteSizeFor finds a suitable paletteSize for the amount of values passed n. 105 | func paletteSizeFor(n int) paletteSize { 106 | for _, size := range sizes { 107 | if n <= (1 << size) { 108 | return size 109 | } 110 | } 111 | // Should never happen. 112 | return 0 113 | } 114 | 115 | // uint32s returns the amount of uint32s needed to represent a storage with this palette size. 116 | func (p paletteSize) uint32s() (n int) { 117 | uint32Count := 0 118 | if p != 0 { 119 | // indicesPerUint32 is the amount of indices that may be stored in a single uint32. 120 | indicesPerUint32 := 32 / int(p) 121 | // uint32Count is the amount of uint32s required to store all indices: 4096 indices need to be stored in 122 | // total. 123 | uint32Count = 4096 / indicesPerUint32 124 | } 125 | if p.padded() { 126 | // We've got one of the padded sizes, so the storage has another uint32 to be able to store 127 | // every index. 128 | uint32Count++ 129 | } 130 | return uint32Count 131 | } 132 | -------------------------------------------------------------------------------- /dragonfly/world/game_mode.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | // GameMode represents a game mode that may be assigned to a player. Upon joining the world, players will be 4 | // given the default game mode that the world holds. 5 | // Game modes specify the way that a player interacts with and plays in the world. 6 | type GameMode interface { 7 | // AllowsEditing specifies if a player with this GameMode can edit the World it's in. 8 | AllowsEditing() bool 9 | // AllowsTakingDamage specifies if a player with this GameMode can take damage from other entities. 10 | AllowsTakingDamage() bool 11 | // CreativeInventory specifies if a player with this GameMode has access to the creative inventory. 12 | CreativeInventory() bool 13 | // HasCollision specifies if a player with this GameMode can collide with blocks or entities in the world. 14 | HasCollision() bool 15 | // AllowsFlying specifies if a player with this GameMode can fly freely. 16 | AllowsFlying() bool 17 | // AllowsInteraction specifies if a player with this GameMode can interact with the world through entities or if it 18 | // can use items in the world. 19 | AllowsInteraction() bool 20 | // Visible specifies if a player with this GameMode can be visible to other players. If false, the player will be 21 | // invisible under any circumstance. 22 | Visible() bool 23 | } 24 | 25 | var ( 26 | // GameModeSurvival is the survival game mode: Players with this game mode have limited supplies and can break blocks 27 | // after taking some time. 28 | GameModeSurvival survival 29 | // GameModeCreative represents the creative game mode: Players with this game mode have infinite blocks and 30 | // items and can break blocks instantly. Players with creative mode can also fly. 31 | GameModeCreative creative 32 | // GameModeAdventure represents the adventure game mode: Players with this game mode cannot edit the world 33 | // (placing or breaking blocks). 34 | GameModeAdventure adventure 35 | // GameModeSpectator represents the spectator game mode: Players with this game mode cannot interact with the 36 | // world and cannot be seen by other players. spectator players can fly, like creative mode, and can 37 | // move through blocks. 38 | GameModeSpectator spectator 39 | ) 40 | 41 | // survival is the survival game mode: Players with this game mode have limited supplies and can break blocks after 42 | // taking some time. 43 | type survival struct{} 44 | 45 | func (survival) AllowsEditing() bool { return true } 46 | func (survival) AllowsTakingDamage() bool { return true } 47 | func (survival) CreativeInventory() bool { return false } 48 | func (survival) HasCollision() bool { return true } 49 | func (survival) AllowsFlying() bool { return false } 50 | func (survival) AllowsInteraction() bool { return true } 51 | func (survival) Visible() bool { return true } 52 | 53 | // creative represents the creative game mode: Players with this game mode have infinite blocks and 54 | // items and can break blocks instantly. Players with creative mode can also fly. 55 | type creative struct{} 56 | 57 | func (creative) AllowsEditing() bool { return true } 58 | func (creative) AllowsTakingDamage() bool { return false } 59 | func (creative) CreativeInventory() bool { return true } 60 | func (creative) HasCollision() bool { return true } 61 | func (creative) AllowsFlying() bool { return true } 62 | func (creative) AllowsInteraction() bool { return true } 63 | func (creative) Visible() bool { return true } 64 | 65 | // adventure represents the adventure game mode: Players with this game mode cannot edit the world 66 | // (placing or breaking blocks). 67 | type adventure struct{} 68 | 69 | func (adventure) AllowsEditing() bool { return false } 70 | func (adventure) AllowsTakingDamage() bool { return true } 71 | func (adventure) CreativeInventory() bool { return false } 72 | func (adventure) HasCollision() bool { return true } 73 | func (adventure) AllowsFlying() bool { return false } 74 | func (adventure) AllowsInteraction() bool { return true } 75 | func (adventure) Visible() bool { return true } 76 | 77 | // spectator represents the spectator game mode: Players with this game mode cannot interact with the 78 | // world and cannot be seen by other players. spectator players can fly, like creative mode, and can 79 | // move through blocks. 80 | type spectator struct{} 81 | 82 | func (spectator) AllowsEditing() bool { return false } 83 | func (spectator) AllowsTakingDamage() bool { return false } 84 | func (spectator) CreativeInventory() bool { return true } 85 | func (spectator) HasCollision() bool { return false } 86 | func (spectator) AllowsFlying() bool { return true } 87 | func (spectator) AllowsInteraction() bool { return false } 88 | func (spectator) Visible() bool { return false } 89 | -------------------------------------------------------------------------------- /dragonfly/world/block_state.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "bytes" 5 | _ "embed" 6 | "fmt" 7 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 8 | "github.com/sandertv/gophertunnel/minecraft/nbt" 9 | "sort" 10 | "strings" 11 | "unsafe" 12 | ) 13 | 14 | var ( 15 | //go:embed block_states.nbt 16 | blockStateData []byte 17 | // blocks holds a list of all registered Blocks indexed by their runtime ID. Blocks that were not explicitly 18 | // registered are of the type unknownBlock. 19 | blocks []blockState 20 | // stateRuntimeIDs holds a map for looking up the runtime ID of a block by the stateHash it produces. 21 | stateRuntimeIDs = map[stateHash]uint32{} 22 | // nbtBlocks holds a list of NBTer implementations for blocks registered that implement the NBTer interface. 23 | // These are indexed by their runtime IDs. Blocks that do not implement NBTer have a false value in this slice. 24 | nbtBlocks []bool 25 | // randomTickBlocks holds a list of RandomTicker implementations for blocks registered that implement the RandomTicker interface. 26 | // These are indexed by their runtime IDs. Blocks that do not implement RandomTicker have a false value in this slice. 27 | randomTickBlocks []bool 28 | ) 29 | 30 | func init() { 31 | dec := nbt.NewDecoder(bytes.NewBuffer(blockStateData)) 32 | 33 | // Register all block states present in the block_states.nbt file. These are all possible options registered 34 | // blocks may encode to. 35 | var s blockState 36 | for { 37 | if err := dec.Decode(&s); err != nil { 38 | break 39 | } 40 | registerBlockState(s) 41 | } 42 | 43 | chunk.RuntimeIDToState = func(runtimeID uint32) (name string, properties map[string]interface{}, found bool) { 44 | if runtimeID >= uint32(len(blocks)) { 45 | return "", nil, false 46 | } 47 | state := blocks[runtimeID] 48 | if updatedEntry, ok := upgradeAliasEntry(state); ok { 49 | state = updatedEntry 50 | } 51 | return state.Name, state.Properties, true 52 | } 53 | chunk.StateToRuntimeID = func(name string, properties map[string]interface{}) (runtimeID uint32, found bool) { 54 | state := blockState{Name: name, Properties: properties} 55 | if updatedEntry, ok := upgradeAliasEntry(state); ok { 56 | state = updatedEntry 57 | } 58 | rid, ok := stateRuntimeIDs[stateHash{name: state.Name, properties: hashProperties(state.Properties)}] 59 | return rid, ok 60 | } 61 | } 62 | 63 | // registerBlockState registers a new blockState to the states slice. The function panics if the properties the 64 | // blockState hold are invalid or if the blockState was already registered. 65 | func registerBlockState(s blockState) { 66 | h := stateHash{name: s.Name, properties: hashProperties(s.Properties)} 67 | if _, ok := stateRuntimeIDs[h]; ok { 68 | panic(fmt.Sprintf("cannot register the same state twice (%+v)", s)) 69 | } 70 | rid := uint32(len(blocks)) 71 | stateRuntimeIDs[h] = rid 72 | blocks = append(blocks, s) 73 | 74 | nbtBlocks = append(nbtBlocks, false) 75 | randomTickBlocks = append(randomTickBlocks, false) 76 | chunk.FilteringBlocks = append(chunk.FilteringBlocks, 15) 77 | chunk.LightBlocks = append(chunk.LightBlocks, 0) 78 | } 79 | 80 | // blockState holds a combination of a name and properties, together with a version. 81 | type blockState struct { 82 | Name string `nbt:"name"` 83 | Properties map[string]interface{} `nbt:"states"` 84 | Version int32 `nbt:"version"` 85 | } 86 | 87 | // stateHash is a struct that may be used as a map key for block states. It contains the name of the block state 88 | // and an encoded version of the properties. 89 | type stateHash struct { 90 | name, properties string 91 | } 92 | 93 | // HashProperties produces a hash for the block properties held by the blockState. 94 | func hashProperties(properties map[string]interface{}) string { 95 | if properties == nil { 96 | return "" 97 | } 98 | keys := make([]string, 0, len(properties)) 99 | for k := range properties { 100 | keys = append(keys, k) 101 | } 102 | sort.Slice(keys, func(i, j int) bool { 103 | return keys[i] < keys[j] 104 | }) 105 | 106 | var b strings.Builder 107 | for _, k := range keys { 108 | switch v := properties[k].(type) { 109 | case bool: 110 | if v { 111 | b.WriteByte(1) 112 | } else { 113 | b.WriteByte(0) 114 | } 115 | case uint8: 116 | b.WriteByte(v) 117 | case int32: 118 | a := *(*[4]byte)(unsafe.Pointer(&v)) 119 | b.Write(a[:]) 120 | case string: 121 | b.WriteString(v) 122 | default: 123 | // If block encoding is broken, we want to find out as soon as possible. This saves a lot of time 124 | // debugging in-game. 125 | panic(fmt.Sprintf("invalid block property type %T for property %v", v, k)) 126 | } 127 | } 128 | 129 | return b.String() 130 | } 131 | -------------------------------------------------------------------------------- /dragonfly/chunk/chunk.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 5 | "sync" 6 | ) 7 | 8 | // Chunk is a segment in the world with a size of 16x16x256 blocks. A chunk contains multiple sub chunks 9 | // and stores other information such as biomes. 10 | // It is not safe to call methods on Chunk simultaneously from multiple goroutines. 11 | type Chunk struct { 12 | sync.Mutex 13 | // r holds the (vertical) range of the Chunk. It includes both the minimum and maximum coordinates. 14 | r cube.Range 15 | // air is the runtime ID of air. 16 | air uint32 17 | // sub holds all sub chunks part of the chunk. The pointers held by the array are nil if no sub chunk is 18 | // allocated at the indices. 19 | sub []*SubChunk 20 | // biomes is an array of biome IDs. There is one biome ID for every column in the chunk. 21 | biomes []*PalettedStorage 22 | } 23 | 24 | // New initialises a new chunk and returns it, so that it may be used. 25 | func New(air uint32, r cube.Range) *Chunk { 26 | n := (r.Height() >> 4) + 1 27 | sub, biomes := make([]*SubChunk, n), make([]*PalettedStorage, n) 28 | for i := range sub { 29 | sub[i] = NewSubChunk(air) 30 | biomes[i] = emptyStorage(0) 31 | } 32 | return &Chunk{r: r, air: air, sub: sub, biomes: biomes} 33 | } 34 | 35 | // Range returns the cube.Range of the Chunk as passed to New. 36 | func (chunk *Chunk) Range() cube.Range { 37 | return chunk.r 38 | } 39 | 40 | // Sub returns a list of all sub chunks present in the chunk. 41 | func (chunk *Chunk) Sub() []*SubChunk { 42 | return chunk.sub 43 | } 44 | 45 | // Block returns the runtime ID of the block at a given x, y and z in a chunk at the given layer. If no 46 | // sub chunk exists at the given y, the block is assumed to be air. 47 | func (chunk *Chunk) Block(x uint8, y int16, z uint8, layer uint8) uint32 { 48 | sub := chunk.SubChunk(y) 49 | if sub.Empty() || uint8(len(sub.storages)) <= layer { 50 | return chunk.air 51 | } 52 | return sub.storages[layer].At(x, uint8(y), z) 53 | } 54 | 55 | // SetBlock sets the runtime ID of a block at a given x, y and z in a chunk at the given layer. If no 56 | // SubChunk exists at the given y, a new SubChunk is created and the block is set. 57 | func (chunk *Chunk) SetBlock(x uint8, y int16, z uint8, layer uint8, block uint32) { 58 | sub := chunk.sub[chunk.SubIndex(y)] 59 | if uint8(len(sub.storages)) <= layer && block == chunk.air { 60 | // Air was set at n layer, but there were less than n layers, so there already was air there. 61 | // Don't do anything with this, just return. 62 | return 63 | } 64 | sub.Layer(layer).Set(x, uint8(y), z, block) 65 | } 66 | 67 | // Biome returns the biome ID at a specific column in the chunk. 68 | func (chunk *Chunk) Biome(x uint8, y int16, z uint8) uint32 { 69 | return chunk.biomes[chunk.SubIndex(y)].At(x, uint8(y), z) 70 | } 71 | 72 | // SetBiome sets the biome ID at a specific column in the chunk. 73 | func (chunk *Chunk) SetBiome(x uint8, y int16, z uint8, biome uint32) { 74 | chunk.biomes[chunk.SubIndex(y)].Set(x, uint8(y), z, biome) 75 | } 76 | 77 | // Light returns the light level at a specific position in the chunk. 78 | func (chunk *Chunk) Light(x uint8, y int16, z uint8) uint8 { 79 | ux, uy, uz, sub := x&0xf, uint8(y&0xf), z&0xf, chunk.SubChunk(y) 80 | sky := sub.SkyLight(ux, uy, uz) 81 | if sky == 15 { 82 | // The skylight was already on the maximum value, so return it without checking block light. 83 | return sky 84 | } 85 | if block := sub.BlockLight(ux, uy, uz); block > sky { 86 | return block 87 | } 88 | return sky 89 | } 90 | 91 | // SkyLight returns the skylight level at a specific position in the chunk. 92 | func (chunk *Chunk) SkyLight(x uint8, y int16, z uint8) uint8 { 93 | return chunk.SubChunk(y).SkyLight(x&15, uint8(y&15), z&15) 94 | } 95 | 96 | // HighestLightBlocker iterates from the highest non-empty sub chunk downwards to find the Y value of the 97 | // highest block that completely blocks any light from going through. If none is found, the value returned is 98 | // 0. 99 | func (chunk *Chunk) HighestLightBlocker(x, z uint8) int16 { 100 | for index := int16(len(chunk.sub) - 1); index >= 0; index-- { 101 | if sub := chunk.sub[index]; !sub.Empty() { 102 | for y := 15; y >= 0; y-- { 103 | if FilteringBlocks[sub.storages[0].At(x, uint8(y), z)] == 15 { 104 | return int16(y) | chunk.SubY(index) 105 | } 106 | } 107 | } 108 | } 109 | return int16(chunk.r[0]) 110 | } 111 | 112 | // HighestBlock iterates from the highest non-empty sub chunk downwards to find the Y value of the highest 113 | // non-air block at an x and z. If no blocks are present in the column, 0 is returned. 114 | func (chunk *Chunk) HighestBlock(x, z uint8) int16 { 115 | for index := int16(len(chunk.sub) - 1); index >= 0; index-- { 116 | if sub := chunk.sub[index]; !sub.Empty() { 117 | for y := 15; y >= 0; y-- { 118 | if rid := sub.storages[0].At(x, uint8(y), z); rid != chunk.air { 119 | return int16(y) | chunk.SubY(index) 120 | } 121 | } 122 | } 123 | } 124 | return int16(chunk.r[0]) 125 | } 126 | 127 | // Compact compacts the chunk as much as possible, getting rid of any sub chunks that are empty, and compacts 128 | // all storages in the sub chunks to occupy as little space as possible. 129 | // Compact should be called right before the chunk is saved in order to optimise the storage space. 130 | func (chunk *Chunk) Compact() { 131 | for i := range chunk.sub { 132 | chunk.sub[i].compact() 133 | } 134 | } 135 | 136 | // SubChunk finds the correct SubChunk in the Chunk by a Y value. 137 | func (chunk *Chunk) SubChunk(y int16) *SubChunk { 138 | return chunk.sub[chunk.SubIndex(y)] 139 | } 140 | 141 | // SubIndex returns the sub chunk Y index matching the y value passed. 142 | func (chunk *Chunk) SubIndex(y int16) int16 { 143 | return (y - int16(chunk.r[0])) >> 4 144 | } 145 | 146 | // SubY returns the sub chunk Y value matching the index passed. 147 | func (chunk *Chunk) SubY(index int16) int16 { 148 | return (index << 4) + int16(chunk.r[0]) 149 | } 150 | -------------------------------------------------------------------------------- /dragonfly/chunk/encoding.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/sandertv/gophertunnel/minecraft/nbt" 8 | "github.com/sandertv/gophertunnel/minecraft/protocol" 9 | "strings" 10 | ) 11 | 12 | type ( 13 | // Encoding is an encoding type used for Chunk encoding. Implementations of this interface are DiskEncoding and 14 | // NetworkEncoding, which can be used to encode a Chunk to an intermediate disk or network representation respectively. 15 | Encoding interface { 16 | encodePalette(buf *bytes.Buffer, p *Palette, e paletteEncoding) 17 | decodePalette(buf *bytes.Buffer, blockSize paletteSize, e paletteEncoding) (*Palette, error) 18 | network() byte 19 | } 20 | // paletteEncoding is an encoding type used for Chunk encoding. It is used to encode different types of palettes 21 | // (for example, blocks or biomes) differently. 22 | paletteEncoding interface { 23 | encode(buf *bytes.Buffer, v uint32) 24 | decode(buf *bytes.Buffer) (uint32, error) 25 | } 26 | ) 27 | 28 | var ( 29 | // DiskEncoding is the Encoding for writing a Chunk to disk. It writes block palettes using NBT and does not use 30 | // varints. 31 | DiskEncoding diskEncoding 32 | // NetworkEncoding is the Encoding used for sending a Chunk over network. It does not use NBT and writes varints. 33 | NetworkEncoding networkEncoding 34 | // NetworkPersistentEncoding is the Encoding used for sending a Chunk over network. It uses NBT, unlike NetworkEncoding. 35 | NetworkPersistentEncoding networkPersistentEncoding 36 | // BiomePaletteEncoding is the paletteEncoding used for encoding a palette of biomes. 37 | BiomePaletteEncoding biomePaletteEncoding 38 | // BlockPaletteEncoding is the paletteEncoding used for encoding a palette of block states encoded as NBT. 39 | BlockPaletteEncoding blockPaletteEncoding 40 | ) 41 | 42 | // biomePaletteEncoding implements the encoding of biome palettes to disk. 43 | type biomePaletteEncoding struct{} 44 | 45 | func (biomePaletteEncoding) encode(buf *bytes.Buffer, v uint32) { 46 | _ = binary.Write(buf, binary.LittleEndian, v) 47 | } 48 | func (biomePaletteEncoding) decode(buf *bytes.Buffer) (uint32, error) { 49 | var v uint32 50 | return v, binary.Read(buf, binary.LittleEndian, &v) 51 | } 52 | 53 | // blockPaletteEncoding implements the encoding of block palettes to disk. 54 | type blockPaletteEncoding struct{} 55 | 56 | func (blockPaletteEncoding) encode(buf *bytes.Buffer, v uint32) { 57 | // Get the block state registered with the runtime IDs we have in the palette of the block storage 58 | // as we need the name and data value to store. 59 | name, props, _ := RuntimeIDToState(v) 60 | _ = nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian).Encode(blockEntry{Name: name, State: props, Version: CurrentBlockVersion}) 61 | } 62 | func (blockPaletteEncoding) decode(buf *bytes.Buffer) (uint32, error) { 63 | var e blockEntry 64 | if err := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian).Decode(&e); err != nil { 65 | return 0, fmt.Errorf("error decoding block palette entry: %w", err) 66 | } 67 | v, ok := StateToRuntimeID(e.Name, e.State) 68 | if !ok { 69 | return 0, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", e.Name, e.State) 70 | } 71 | return v, nil 72 | } 73 | 74 | // diskEncoding implements the Chunk encoding for writing to disk. 75 | type diskEncoding struct{} 76 | 77 | func (diskEncoding) network() byte { return 0 } 78 | func (diskEncoding) encodePalette(buf *bytes.Buffer, p *Palette, e paletteEncoding) { 79 | if p.size != 0 { 80 | _ = binary.Write(buf, binary.LittleEndian, uint32(p.Len())) 81 | } 82 | for _, v := range p.values { 83 | e.encode(buf, v) 84 | } 85 | } 86 | func (diskEncoding) decodePalette(buf *bytes.Buffer, blockSize paletteSize, e paletteEncoding) (*Palette, error) { 87 | paletteCount := uint32(1) 88 | if blockSize != 0 { 89 | if err := binary.Read(buf, binary.LittleEndian, &paletteCount); err != nil { 90 | return nil, fmt.Errorf("error reading palette entry count: %w", err) 91 | } 92 | } 93 | 94 | var err error 95 | palette := newPalette(blockSize, make([]uint32, paletteCount)) 96 | for i := uint32(0); i < paletteCount; i++ { 97 | palette.values[i], err = e.decode(buf) 98 | if err != nil { 99 | return nil, err 100 | } 101 | } 102 | return palette, nil 103 | } 104 | 105 | // networkEncoding implements the Chunk encoding for sending over network. 106 | type networkEncoding struct{} 107 | 108 | func (networkEncoding) network() byte { return 1 } 109 | func (networkEncoding) encodePalette(buf *bytes.Buffer, p *Palette, _ paletteEncoding) { 110 | if p.size != 0 { 111 | _ = protocol.WriteVarint32(buf, int32(p.Len())) 112 | } 113 | for _, val := range p.values { 114 | _ = protocol.WriteVarint32(buf, int32(val)) 115 | } 116 | } 117 | func (networkEncoding) decodePalette(buf *bytes.Buffer, blockSize paletteSize, _ paletteEncoding) (*Palette, error) { 118 | var paletteCount int32 = 1 119 | if blockSize != 0 { 120 | if err := protocol.Varint32(buf, &paletteCount); err != nil { 121 | return nil, fmt.Errorf("error reading palette entry count: %w", err) 122 | } 123 | if paletteCount <= 0 { 124 | return nil, fmt.Errorf("invalid palette entry count %v", paletteCount) 125 | } 126 | } 127 | 128 | var err error 129 | palette, temp := newPalette(blockSize, make([]uint32, paletteCount)), int32(0) 130 | for i := int32(0); i < paletteCount; i++ { 131 | if err = protocol.Varint32(buf, &temp); err != nil { 132 | return nil, fmt.Errorf("error decoding palette entry: %w", err) 133 | } 134 | palette.values[i] = uint32(temp) 135 | } 136 | return palette, nil 137 | } 138 | 139 | // networkPersistentEncoding implements the Chunk encoding for sending over network with persistent storage. 140 | type networkPersistentEncoding struct{} 141 | 142 | func (networkPersistentEncoding) network() byte { return 1 } 143 | func (networkPersistentEncoding) encodePalette(buf *bytes.Buffer, p *Palette, _ paletteEncoding) { 144 | if p.size != 0 { 145 | _ = protocol.WriteVarint32(buf, int32(p.Len())) 146 | } 147 | 148 | enc := nbt.NewEncoderWithEncoding(buf, nbt.NetworkLittleEndian) 149 | for _, val := range p.values { 150 | name, props, _ := RuntimeIDToState(val) 151 | _ = enc.Encode(blockEntry{Name: strings.TrimPrefix("minecraft:", name), State: props, Version: CurrentBlockVersion}) 152 | } 153 | } 154 | func (networkPersistentEncoding) decodePalette(buf *bytes.Buffer, blockSize paletteSize, _ paletteEncoding) (*Palette, error) { 155 | var paletteCount int32 = 1 156 | if blockSize != 0 { 157 | err := protocol.Varint32(buf, &paletteCount) 158 | if err != nil { 159 | panic(err) 160 | } 161 | if paletteCount <= 0 { 162 | return nil, fmt.Errorf("invalid palette entry count %v", paletteCount) 163 | } 164 | } 165 | 166 | blocks := make([]blockEntry, paletteCount) 167 | dec := nbt.NewDecoderWithEncoding(buf, nbt.NetworkLittleEndian) 168 | for i := int32(0); i < paletteCount; i++ { 169 | if err := dec.Decode(&blocks[i]); err != nil { 170 | return nil, fmt.Errorf("error decoding block state: %w", err) 171 | } 172 | } 173 | 174 | var ok bool 175 | palette, temp := newPalette(blockSize, make([]uint32, paletteCount)), uint32(0) 176 | for i, b := range blocks { 177 | temp, ok = StateToRuntimeID("minecraft:"+b.Name, b.State) 178 | if !ok { 179 | return nil, fmt.Errorf("cannot get runtime ID of block state %v{%+v}", b.Name, b.State) 180 | } 181 | palette.values[i] = temp 182 | } 183 | return palette, nil 184 | } 185 | -------------------------------------------------------------------------------- /dragonfly/mcdb/level_dat.go: -------------------------------------------------------------------------------- 1 | package mcdb 2 | 3 | // data holds a collection of data that specify a range of settings of the world. These settings usually 4 | // alter the way that players interact with the world. 5 | // The data held here is usually saved in a level.dat file of the world. 6 | //noinspection SpellCheckingInspection 7 | type data struct { 8 | BaseGameVersion string `nbt:"baseGameVersion"` 9 | BiomeOverride string 10 | ConfirmedPlatformLockedContent bool 11 | CenterMapsToOrigin bool 12 | Difficulty int32 13 | EduOffer int32 `nbt:"eduOffer"` 14 | FlatWorldLayers string 15 | ForceGameType bool 16 | GameType int32 17 | Generator int32 18 | InventoryVersion string 19 | LANBroadcast bool 20 | LANBroadcastIntent bool 21 | LastPlayed int64 22 | LevelName string 23 | LimitedWorldOriginX int32 24 | LimitedWorldOriginY int32 25 | LimitedWorldOriginZ int32 26 | LimitedWorldDepth int32 `nbt:"limitedWorldDepth"` 27 | LimitedWorldWidth int32 `nbt:"limitedWorldWidth"` 28 | MinimumCompatibleClientVersion []int32 29 | MultiPlayerGame bool `nbt:"MultiplayerGame"` 30 | MultiPlayerGameIntent bool `nbt:"MultiplayerGameIntent"` 31 | NetherScale int32 32 | NetworkVersion int32 33 | Platform int32 34 | PlatformBroadcastIntent int32 35 | RandomSeed int64 36 | ShowTags bool `nbt:"showtags"` 37 | SingleUseWorld bool `nbt:"isSingleUseWorld"` 38 | SpawnX, SpawnY, SpawnZ int32 39 | SpawnV1Villagers bool 40 | StorageVersion int32 41 | Time int64 42 | XBLBroadcast bool 43 | XBLBroadcastIntent int32 44 | XBLBroadcastMode int32 45 | Abilities struct { 46 | AttackMobs bool `nbt:"attackmobs"` 47 | AttackPlayers bool `nbt:"attackplayers"` 48 | Build bool `nbt:"build"` 49 | Mine bool `nbt:"mine"` 50 | DoorsAndSwitches bool `nbt:"doorsandswitches"` 51 | FlySpeed float32 `nbt:"flySpeed"` 52 | Flying bool `nbt:"flying"` 53 | InstantBuild bool `nbt:"instabuild"` 54 | Invulnerable bool `nbt:"invulnerable"` 55 | Lightning bool `nbt:"lightning"` 56 | MayFly bool `nbt:"mayfly"` 57 | OP bool `nbt:"op"` 58 | OpenContainers bool `nbt:"opencontainers"` 59 | PermissionsLevel int32 `nbt:"permissionsLevel"` 60 | PlayerPermissionsLevel int32 `nbt:"playerPermissionsLevel"` 61 | Teleport bool `nbt:"teleport"` 62 | WalkSpeed float32 `nbt:"walkSpeed"` 63 | } `nbt:"abilities"` 64 | BonusChestEnabled bool `nbt:"bonusChestEnabled"` 65 | BonusChestSpawned bool `nbt:"bonusChestSpawned"` 66 | CommandBlockOutput bool `nbt:"commandblockoutput"` 67 | CommandBlocksEnabled bool `nbt:"commandblocksenabled"` 68 | CommandsEnabled bool `nbt:"commandsEnabled"` 69 | CurrentTick int64 `nbt:"currentTick"` 70 | DoDayLightCycle bool `nbt:"dodaylightcycle"` 71 | DoEntityDrops bool `nbt:"doentitydrops"` 72 | DoFireTick bool `nbt:"dofiretick"` 73 | DoImmediateRespawn bool `nbt:"doimmediaterespawn"` 74 | DoInsomnia bool `nbt:"doinsomnia"` 75 | DoMobLoot bool `nbt:"domobloot"` 76 | DoMobSpawning bool `nbt:"domobspawning"` 77 | DoTileDrops bool `nbt:"dotiledrops"` 78 | DoWeatherCycle bool `nbt:"doweathercycle"` 79 | DrowningDamage bool `nbt:"drowningdamage"` 80 | EduLevel bool `nbt:"eduLevel"` 81 | EducationFeaturesEnabled bool `nbt:"educationFeaturesEnabled"` 82 | ExperimentalGamePlay bool `nbt:"experimentalgameplay"` 83 | FallDamage bool `nbt:"falldamage"` 84 | FireDamage bool `nbt:"firedamage"` 85 | FunctionCommandLimit int32 `nbt:"functioncommandlimit"` 86 | HasBeenLoadedInCreative bool `nbt:"hasBeenLoadedInCreative"` 87 | HasLockedBehaviourPack bool `nbt:"hasLockedBehaviorPack"` 88 | HasLockedResourcePack bool `nbt:"hasLockedResourcePack"` 89 | ImmutableWorld bool `nbt:"immutableWorld"` 90 | IsFromLockedTemplate bool `nbt:"isFromLockedTemplate"` 91 | IsFromWorldTemplate bool `nbt:"isFromWorldTemplate"` 92 | IsWorldTemplateOptionLocked bool `nbt:"isWorldTemplateOptionLocked"` 93 | KeepInventory bool `nbt:"keepinventory"` 94 | LastOpenedWithVersion []int32 `nbt:"lastOpenedWithVersion"` 95 | LightningLevel float32 `nbt:"lightningLevel"` 96 | LightningTime int32 `nbt:"lightningTime"` 97 | MaxCommandChainLength int32 `nbt:"maxcommandchainlength"` 98 | MobGriefing bool `nbt:"mobgriefing"` 99 | NaturalRegeneration bool `nbt:"naturalregeneration"` 100 | PRID string `nbt:"prid"` 101 | PVP bool `nbt:"pvp"` 102 | RainLevel float32 `nbt:"rainLevel"` 103 | RainTime int32 `nbt:"rainTime"` 104 | RandomTickSpeed int32 `nbt:"randomtickspeed"` 105 | RequiresCopiedPackRemovalCheck bool `nbt:"requiresCopiedPackRemovalCheck"` 106 | SendCommandFeedback bool `nbt:"sendcommandfeedback"` 107 | ServerChunkTickRange int32 `nbt:"serverChunkTickRange"` 108 | ShowCoordinates bool `nbt:"showcoordinates"` 109 | ShowDeathMessages bool `nbt:"showdeathmessages"` 110 | SpawnMobs bool `nbt:"spawnMobs"` 111 | SpawnRadius int32 `nbt:"spawnradius"` 112 | StartWithMapEnabled bool `nbt:"startWithMapEnabled"` 113 | TexturePacksRequired bool `nbt:"texturePacksRequired"` 114 | TNTExplodes bool `nbt:"tntexplodes"` 115 | UseMSAGamerTagsOnly bool `nbt:"useMsaGamertagsOnly"` 116 | WorldStartCount int64 `nbt:"worldStartCount"` 117 | Experiments map[string]interface{} `nbt:"experiments"` 118 | FreezeDamage uint8 `nbt:"freezedamage"` 119 | WorldPolicies map[string]interface{} `nbt:"world_policies"` 120 | WorldVersion int32 `nbt:"WorldVersion"` 121 | RespawnBlocksExplode bool `nbt:"respawnblocksexplode"` 122 | ShowBorderEffect bool `nbt:"showbordereffect"` 123 | } 124 | -------------------------------------------------------------------------------- /dragonfly/chunk/paletted_storage.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | // uint32ByteSize is the amount of bytes in a uint32. 10 | uint32ByteSize = 4 11 | // uint32BitSize is the amount of bits in a uint32. 12 | uint32BitSize = uint32ByteSize * 8 13 | ) 14 | 15 | // PalettedStorage is a storage of 4096 blocks encoded in a variable amount of uint32s, storages may have values 16 | // with a bit size per block of 0, 1, 2, 3, 4, 5, 6, 8 or 16 bits. 17 | // 3 of these formats have additional padding in every uint32 and an additional uint32 at the end, to cater 18 | // for the blocks that don't fit. This padding is present when the storage has a block size of 3, 5 or 6 19 | // bytes. 20 | // Methods on PalettedStorage must not be called simultaneously from multiple goroutines. 21 | type PalettedStorage struct { 22 | // bitsPerIndex is the amount of bits required to store one block. The number increases as the block 23 | // storage holds more unique block states. 24 | bitsPerIndex uint16 25 | // filledBitsPerIndex returns the amount of blocks that are actually filled per uint32. 26 | filledBitsPerIndex uint16 27 | // indexMask is the equivalent of 1 << bitsPerIndex - 1. 28 | indexMask uint32 29 | 30 | // indicesStart holds an unsafe.Pointer to the first byte in the indices slice below. 31 | indicesStart unsafe.Pointer 32 | 33 | // Palette holds all block runtime IDs that the indices in the indices slice point to. These runtime IDs 34 | // point to block states. 35 | palette *Palette 36 | 37 | // indices contains all indices in the PalettedStorage. This slice has a variable size, but may not be changed 38 | // unless the whole PalettedStorage is resized, including the Palette. 39 | indices []uint32 40 | } 41 | 42 | // newPalettedStorage creates a new block storage using the uint32 slice as the indices and the palette passed. 43 | // The bits per block are calculated using the length of the uint32 slice. 44 | func newPalettedStorage(indices []uint32, palette *Palette) *PalettedStorage { 45 | var ( 46 | bitsPerIndex = uint16(len(indices) / uint32BitSize / uint32ByteSize) 47 | indexMask = (uint32(1) << bitsPerIndex) - 1 48 | indicesStart = (unsafe.Pointer)((*reflect.SliceHeader)(unsafe.Pointer(&indices)).Data) 49 | filledBitsPerIndex uint16 50 | ) 51 | if bitsPerIndex != 0 { 52 | filledBitsPerIndex = uint32BitSize / bitsPerIndex * bitsPerIndex 53 | } 54 | return &PalettedStorage{filledBitsPerIndex: filledBitsPerIndex, indexMask: indexMask, indicesStart: indicesStart, bitsPerIndex: bitsPerIndex, indices: indices, palette: palette} 55 | } 56 | 57 | // emptyStorage creates a PalettedStorage filled completely with a value v. 58 | func emptyStorage(v uint32) *PalettedStorage { 59 | return newPalettedStorage([]uint32{}, newPalette(0, []uint32{v})) 60 | } 61 | 62 | // Palette returns the Palette of the PalettedStorage. 63 | func (storage *PalettedStorage) Palette() *Palette { 64 | return storage.palette 65 | } 66 | 67 | // At returns the value of the PalettedStorage at a given x, y and z. 68 | func (storage *PalettedStorage) At(x, y, z byte) uint32 { 69 | return storage.palette.Value(storage.paletteIndex(x&15, y&15, z&15)) 70 | } 71 | 72 | // Set sets a value at a specific x, y and z. The Palette and PalettedStorage are expanded 73 | // automatically to make space for the value, should that be needed. 74 | func (storage *PalettedStorage) Set(x, y, z byte, v uint32) { 75 | index := storage.palette.Index(v) 76 | if index == -1 { 77 | // The runtime ID was not yet available in the palette. We add it, then check if the block storage 78 | // needs to be resized for the palette pointers to fit. 79 | index = storage.addNew(v) 80 | } 81 | storage.setPaletteIndex(x&15, y&15, z&15, uint16(index)) 82 | } 83 | 84 | // addNew adds a new value to the PalettedStorage's Palette and returns its index. If needed, the storage is resized. 85 | func (storage *PalettedStorage) addNew(v uint32) int16 { 86 | index, resize := storage.palette.Add(v) 87 | if resize { 88 | storage.resize(storage.palette.size) 89 | } 90 | return index 91 | } 92 | 93 | // paletteIndex looks up the Palette index at a given x, y and z value in the PalettedStorage. This palette 94 | // index is not the value at this offset, but merely an index in the Palette pointing to a value. 95 | func (storage *PalettedStorage) paletteIndex(x, y, z byte) uint16 { 96 | if storage.bitsPerIndex == 0 { 97 | // Unfortunately our default logic cannot deal with 0 bits per index, meaning we'll have to special case 98 | // this. This comes with a little performance hit, but it seems to be the only way to go. An alternative would 99 | // be not to have 0 bits per block storages in memory, but that would cause a strongly increased memory usage 100 | // by biomes. 101 | return 0 102 | } 103 | offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex 104 | uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex 105 | 106 | w := *(*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2))) 107 | return uint16((w >> bitOffset) & storage.indexMask) 108 | } 109 | 110 | // setPaletteIndex sets the palette index at a given x, y and z to paletteIndex. This index should point 111 | // to a value in the PalettedStorage's Palette. 112 | func (storage *PalettedStorage) setPaletteIndex(x, y, z byte, i uint16) { 113 | if storage.bitsPerIndex == 0 { 114 | return 115 | } 116 | offset := ((uint16(x) << 8) | (uint16(z) << 4) | uint16(y)) * storage.bitsPerIndex 117 | uint32Offset, bitOffset := offset/storage.filledBitsPerIndex, offset%storage.filledBitsPerIndex 118 | 119 | ptr := (*uint32)(unsafe.Pointer(uintptr(storage.indicesStart) + uintptr(uint32Offset<<2))) 120 | *ptr = (*ptr &^ (storage.indexMask << bitOffset)) | (uint32(i) << bitOffset) 121 | } 122 | 123 | // resize changes the size of a PalettedStorage to newPaletteSize. A new PalettedStorage is constructed, 124 | // and all values available in the current storage are set in their appropriate locations in the 125 | // new storage. 126 | func (storage *PalettedStorage) resize(newPaletteSize paletteSize) { 127 | if newPaletteSize == paletteSize(storage.bitsPerIndex) { 128 | return // Don't resize if the size is already equal. 129 | } 130 | // Construct a new storage and set all values in there manually. We can't easily do this in a better 131 | // way, because all values will be at a different index with a different length. 132 | newStorage := newPalettedStorage(make([]uint32, newPaletteSize.uint32s()), storage.palette) 133 | for x := byte(0); x < 16; x++ { 134 | for y := byte(0); y < 16; y++ { 135 | for z := byte(0); z < 16; z++ { 136 | newStorage.setPaletteIndex(x, y, z, storage.paletteIndex(x, y, z)) 137 | } 138 | } 139 | } 140 | // Set the new storage. 141 | *storage = *newStorage 142 | } 143 | 144 | // compact clears unused indexes in the palette by scanning for usages in the PalettedStorage. This is a 145 | // relatively heavy task which should only happen right before the sub chunk holding this PalettedStorage is 146 | // saved to disk. compact also shrinks the palette size if possible. 147 | func (storage *PalettedStorage) compact() { 148 | usedIndices := make([]bool, storage.palette.Len()) 149 | for x := byte(0); x < 16; x++ { 150 | for y := byte(0); y < 16; y++ { 151 | for z := byte(0); z < 16; z++ { 152 | usedIndices[storage.paletteIndex(x, y, z)] = true 153 | } 154 | } 155 | } 156 | newRuntimeIDs := make([]uint32, 0, len(usedIndices)) 157 | conversion := make([]uint16, len(usedIndices)) 158 | 159 | for index, set := range usedIndices { 160 | if set { 161 | conversion[index] = uint16(len(newRuntimeIDs)) 162 | newRuntimeIDs = append(newRuntimeIDs, storage.palette.values[index]) 163 | } 164 | } 165 | // Construct a new storage and set all values in there manually. We can't easily do this in a better 166 | // way, because all values will be at a different index with a different length. 167 | size := paletteSizeFor(len(newRuntimeIDs)) 168 | newStorage := newPalettedStorage(make([]uint32, size.uint32s()), newPalette(size, newRuntimeIDs)) 169 | 170 | for x := byte(0); x < 16; x++ { 171 | for y := byte(0); y < 16; y++ { 172 | for z := byte(0); z < 16; z++ { 173 | // Replace all usages of the old palette indexes with the new indexes using the map we 174 | // produced earlier. 175 | newStorage.setPaletteIndex(x, y, z, conversion[storage.paletteIndex(x, y, z)]) 176 | } 177 | } 178 | } 179 | *storage = *newStorage 180 | } 181 | -------------------------------------------------------------------------------- /dragonfly/chunk/decode.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 7 | ) 8 | 9 | // StateToRuntimeID must hold a function to convert a name and its state properties to a runtime ID. 10 | var StateToRuntimeID func(name string, properties map[string]interface{}) (runtimeID uint32, found bool) 11 | 12 | // NetworkDecode decodes the network serialised data passed into a Chunk if successful. If not, the chunk 13 | // returned is nil and the error non-nil. 14 | // The sub chunk count passed must be that found in the LevelChunk packet. 15 | //noinspection GoUnusedExportedFunction 16 | func NetworkDecode(air uint32, data []byte, count int, oldBiomes bool, r cube.Range) (*Chunk, error) { 17 | var ( 18 | c = New(air, r) 19 | buf = bytes.NewBuffer(data) 20 | err error 21 | ) 22 | for i := 0; i < count; i++ { 23 | index := uint8(i) 24 | c.sub[index], err = DecodeSubChunk(buf, c, &index, NetworkEncoding) 25 | if err != nil { 26 | return nil, err 27 | } 28 | } 29 | if oldBiomes { 30 | // Read the old biomes. 31 | biomes := make([]byte, 256) 32 | if _, err := buf.Read(biomes[:]); err != nil { 33 | return nil, fmt.Errorf("error reading biomes: %w", err) 34 | } 35 | 36 | // Make our 2D biomes 3D. 37 | for x := 0; x < 16; x++ { 38 | for z := 0; z < 16; z++ { 39 | id := biomes[(x&15)|(z&15)<<4] 40 | for y := r.Min(); y <= r.Max(); y++ { 41 | c.SetBiome(uint8(x), int16(y), uint8(z), uint32(id)) 42 | } 43 | } 44 | } 45 | } else { 46 | var last *PalettedStorage 47 | for i := 0; i < len(c.sub); i++ { 48 | b, err := decodePalettedStorage(buf, NetworkEncoding, BiomePaletteEncoding) 49 | if err != nil { 50 | return nil, err 51 | } 52 | // b == nil means this paletted storage had the flag pointing to the previous one. It basically means we should 53 | // inherit whatever palette we decoded last. 54 | if i == 0 && b == nil { 55 | // This should never happen and there is no way to handle this. 56 | return nil, fmt.Errorf("first biome storage pointed to previous one") 57 | } 58 | if b == nil { 59 | // This means this paletted storage had the flag pointing to the previous one. It basically means we should 60 | // inherit whatever palette we decoded last. 61 | b = last 62 | } else { 63 | last = b 64 | } 65 | c.biomes[i] = b 66 | } 67 | } 68 | return c, nil 69 | } 70 | 71 | // DiskDecode decodes the data from a SerialisedData object into a chunk and returns it. If the data was 72 | // invalid, an error is returned. 73 | func DiskDecode(data SerialisedData, r cube.Range) (*Chunk, error) { 74 | air, ok := StateToRuntimeID("minecraft:air", nil) 75 | if !ok { 76 | panic("cannot find air runtime ID") 77 | } 78 | 79 | c := New(air, r) 80 | 81 | err := decodeBiomes(bytes.NewBuffer(data.Biomes), c, DiskEncoding) 82 | if err != nil { 83 | return nil, err 84 | } 85 | for i, sub := range data.SubChunks { 86 | if len(sub) == 0 { 87 | // No data for this sub chunk. 88 | continue 89 | } 90 | index := uint8(i) 91 | if c.sub[index], err = DecodeSubChunk(bytes.NewBuffer(sub), c, &index, DiskEncoding); err != nil { 92 | return nil, err 93 | } 94 | } 95 | return c, nil 96 | } 97 | 98 | // DecodeSubChunk decodes a SubChunk from a bytes.Buffer. The Encoding passed defines how the block storages of the 99 | // SubChunk are decoded. 100 | func DecodeSubChunk(buf *bytes.Buffer, c *Chunk, index *byte, e Encoding) (*SubChunk, error) { 101 | ver, err := buf.ReadByte() 102 | if err != nil { 103 | return nil, fmt.Errorf("error reading version: %w", err) 104 | } 105 | sub := NewSubChunk(c.air) 106 | switch ver { 107 | default: 108 | return nil, fmt.Errorf("unknown sub chunk version %v: can't decode", ver) 109 | case 0: 110 | ids := buf.Next(4096) 111 | if len(ids) != 4096 { 112 | return nil, fmt.Errorf("length of ids is smaller than 4096") 113 | } 114 | 115 | metadata := buf.Next(2048) 116 | if len(metadata) != 2048 { 117 | return nil, fmt.Errorf("length of metadata is smaller than 2048") 118 | } 119 | 120 | storage := newPalettedStorage(make([]uint32, 512), newPalette(4, make([]uint32, 0, 4))) 121 | for x := uint16(0); x < 16; x++ { 122 | for z := uint16(0); z < 16; z++ { 123 | for y := uint16(0); y < 8; y++ { 124 | i := (x << 8) | (z << 4) | (y << 1) 125 | 126 | meta := metadata[i>>1] 127 | 128 | err = setBlockData(storage, ids[i], meta&0xf, byte(x), byte(y<<1), byte(z)) 129 | if err != nil { 130 | return nil, err 131 | } 132 | err = setBlockData(storage, ids[i|1], (meta>>4)&0xf, byte(x), byte((y<<1)|1), byte(z)) 133 | if err != nil { 134 | return nil, err 135 | } 136 | } 137 | } 138 | } 139 | 140 | sub.storages = append(sub.storages, storage) 141 | case 8, 9: 142 | // Version 8 allows up to 256 layers for one sub chunk. 143 | storageCount, err := buf.ReadByte() 144 | if err != nil { 145 | return nil, fmt.Errorf("error reading storage count: %w", err) 146 | } 147 | if ver == 9 { 148 | uIndex, err := buf.ReadByte() 149 | if err != nil { 150 | return nil, fmt.Errorf("error reading subchunk index: %w", err) 151 | } 152 | // The index as written here isn't the actual index of the subchunk within the chunk. Rather, it is the Y 153 | // value of the subchunk. This means that we need to translate it to an index. 154 | *index = uint8(int8(uIndex) - int8(c.r[0]>>4)) 155 | } 156 | sub.storages = make([]*PalettedStorage, storageCount) 157 | 158 | for i := byte(0); i < storageCount; i++ { 159 | sub.storages[i], err = decodePalettedStorage(buf, e, BlockPaletteEncoding) 160 | if err != nil { 161 | return nil, err 162 | } 163 | } 164 | } 165 | return sub, nil 166 | } 167 | 168 | // setBlockData sets block data in a block storage instance, with it's ID, meta, and block position. 169 | // It returns an error, which should be nil if everything was successful. 170 | func setBlockData(storage *PalettedStorage, blockId, meta, x, y, z byte) error { 171 | state, ok := conversion[oldBlock{id: blockId, metadata: meta}] 172 | if !ok { 173 | // Try cases where meta should be ignored. 174 | state, ok = conversion[oldBlock{id: blockId}] 175 | if !ok { 176 | return fmt.Errorf("can't find runtime ID for id and meta: %v, %v", blockId, meta) 177 | } 178 | } 179 | 180 | id, ok := StateToRuntimeID(state.name, state.properties) 181 | if !ok { 182 | // Fuck this shit. 183 | fmt.Println(state.name, state.properties) 184 | id, _ = StateToRuntimeID("minecraft:air", nil) 185 | } 186 | 187 | storage.Set(x, y, z, id) 188 | return nil 189 | } 190 | 191 | // decodeBiomes reads the paletted storages holding biomes from buf and stores it into the Chunk passed. 192 | func decodeBiomes(buf *bytes.Buffer, c *Chunk, e Encoding) error { 193 | var last *PalettedStorage 194 | if buf.Len() != 0 { 195 | for i := 0; i < len(c.sub); i++ { 196 | b, err := decodePalettedStorage(buf, e, BiomePaletteEncoding) 197 | if err != nil { 198 | return err 199 | } 200 | // b == nil means this paletted storage had the flag pointing to the previous one. It basically means we should 201 | // inherit whatever palette we decoded last. 202 | if i == 0 && b == nil { 203 | // This should never happen and there is no way to handle this. 204 | return fmt.Errorf("first biome storage pointed to previous one") 205 | } 206 | if b == nil { 207 | // This means this paletted storage had the flag pointing to the previous one. It basically means we should 208 | // inherit whatever palette we decoded last. 209 | b = last 210 | } else { 211 | last = b 212 | } 213 | c.biomes[i] = b 214 | } 215 | } 216 | return nil 217 | } 218 | 219 | // decodePalettedStorage decodes a PalettedStorage from a bytes.Buffer. The Encoding passed is used to read either a 220 | // network or disk block storage. 221 | func decodePalettedStorage(buf *bytes.Buffer, e Encoding, pe paletteEncoding) (*PalettedStorage, error) { 222 | blockSize, err := buf.ReadByte() 223 | if err != nil { 224 | return nil, fmt.Errorf("error reading block size: %w", err) 225 | } 226 | if e == NetworkEncoding && blockSize&1 != 1 { 227 | e = NetworkPersistentEncoding 228 | } 229 | 230 | blockSize >>= 1 231 | if blockSize == 0x7f { 232 | return nil, nil 233 | } 234 | 235 | size := paletteSize(blockSize) 236 | uint32Count := size.uint32s() 237 | 238 | uint32s := make([]uint32, uint32Count) 239 | byteCount := uint32Count * 4 240 | 241 | data := buf.Next(byteCount) 242 | if len(data) != byteCount { 243 | return nil, fmt.Errorf("cannot read paletted storage (size=%v) %T: not enough block data present: expected %v bytes, got %v", blockSize, pe, byteCount, len(data)) 244 | } 245 | for i := 0; i < uint32Count; i++ { 246 | // Explicitly don't use the binary package to greatly improve performance of reading the uint32s. 247 | uint32s[i] = uint32(data[i*4]) | uint32(data[i*4+1])<<8 | uint32(data[i*4+2])<<16 | uint32(data[i*4+3])<<24 248 | } 249 | p, err := e.decodePalette(buf, paletteSize(blockSize), pe) 250 | return newPalettedStorage(uint32s, p), err 251 | } 252 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/go-gl/mathgl/mgl64" 9 | "github.com/hajimehoshi/ebiten/v2" 10 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 11 | "github.com/justtaldevelops/worldcompute/dragonfly/mcdb" 12 | "github.com/justtaldevelops/worldcompute/dragonfly/world" 13 | "github.com/justtaldevelops/worldcompute/worldrenderer" 14 | "github.com/pelletier/go-toml" 15 | "github.com/sandertv/gophertunnel/minecraft" 16 | "github.com/sandertv/gophertunnel/minecraft/auth" 17 | "github.com/sandertv/gophertunnel/minecraft/protocol" 18 | "github.com/sandertv/gophertunnel/minecraft/protocol/packet" 19 | "github.com/sandertv/gophertunnel/minecraft/text" 20 | "github.com/sirupsen/logrus" 21 | "golang.org/x/oauth2" 22 | "io/ioutil" 23 | "os" 24 | "strings" 25 | "sync" 26 | ) 27 | 28 | var ( 29 | mu sync.Mutex 30 | chunks = make(map[world.ChunkPos]*chunk.Chunk) 31 | renderer *worldrenderer.Renderer 32 | ) 33 | 34 | // main starts the renderer and proxy. 35 | func main() { 36 | log := logrus.New() 37 | log.Formatter = &logrus.TextFormatter{ForceColors: true} 38 | log.Level = logrus.DebugLevel 39 | 40 | src := tokenSource() 41 | conf, err := readConfig() 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | go func() { 47 | log.Println("worldcompute has loaded. connect to " + conf.Connection.LocalAddress) 48 | log.Println("redirecting connections to " + conf.Connection.RemoteAddress) 49 | 50 | p, err := minecraft.NewForeignStatusProvider(conf.Connection.RemoteAddress) 51 | if err != nil { 52 | panic(err) 53 | } 54 | listener, err := minecraft.ListenConfig{ 55 | StatusProvider: p, 56 | }.Listen("raknet", conf.Connection.LocalAddress) 57 | if err != nil { 58 | panic(err) 59 | } 60 | defer listener.Close() 61 | 62 | for { 63 | c, err := listener.Accept() 64 | if err != nil { 65 | panic(err) 66 | } 67 | go handleConn(log, c.(*minecraft.Conn), listener, conf, src) 68 | } 69 | }() 70 | 71 | renderer = worldrenderer.NewRendererDirect(4, 6.5, mgl64.Vec2{}, &mu, chunks) 72 | 73 | ebiten.SetWindowSize(1718, 1360) 74 | ebiten.SetWindowResizable(true) 75 | ebiten.SetWindowTitle("worldrenderer") 76 | if err := ebiten.RunGame(renderer); err != nil { 77 | log.Fatal(err) 78 | } 79 | } 80 | 81 | // handleConn handles a new incoming minecraft.Conn from the minecraft.Listener passed. 82 | func handleConn(log *logrus.Logger, conn *minecraft.Conn, listener *minecraft.Listener, config config, src oauth2.TokenSource) { 83 | clientData := conn.ClientData() 84 | clientData.ServerAddress = config.Connection.RemoteAddress 85 | 86 | serverConn, err := minecraft.Dialer{ 87 | TokenSource: src, 88 | ClientData: clientData, 89 | }.Dial("raknet", config.Connection.RemoteAddress) 90 | if err != nil { 91 | log.Errorf("error connecting to %s: %v", config.Connection.RemoteAddress, err) 92 | return 93 | } 94 | 95 | data := serverConn.GameData() 96 | data.GameRules = append(data.GameRules, []protocol.GameRule{{Name: "showCoordinates", Value: true}}...) 97 | 98 | airRID, _ := chunk.StateToRuntimeID("minecraft:air", nil) 99 | oldFormat := data.BaseGameVersion == "1.17.40" 100 | 101 | pos := data.PlayerPosition 102 | dimension := world.Dimension(world.Overworld) 103 | switch data.Dimension { 104 | case 1: 105 | dimension = world.Nether 106 | case 2: 107 | dimension = world.End 108 | } 109 | 110 | renderer.Recenter(mgl64.Vec2{ 111 | float64(pos.X()), 112 | float64(pos.Z()), 113 | }) 114 | 115 | log.Println("completed connection to " + config.Connection.RemoteAddress) 116 | 117 | var g sync.WaitGroup 118 | g.Add(2) 119 | go func() { 120 | if err := conn.StartGame(data); err != nil { 121 | log.Errorf("error starting game: %v", err) 122 | return 123 | } 124 | g.Done() 125 | }() 126 | go func() { 127 | if err := serverConn.DoSpawn(); err != nil { 128 | log.Errorf("error spawning: %v", err) 129 | return 130 | } 131 | g.Done() 132 | }() 133 | g.Wait() 134 | 135 | log.Printf("successfully spawned in to %s", config.Connection.RemoteAddress) 136 | go func() { 137 | defer listener.Disconnect(conn, "connection lost") 138 | defer serverConn.Close() 139 | for { 140 | pk, err := conn.ReadPacket() 141 | if err != nil { 142 | return 143 | } 144 | switch pk := pk.(type) { 145 | case *packet.PlayerAuthInput: 146 | pos = pk.Position 147 | renderer.Recenter(mgl64.Vec2{ 148 | float64(pos.X()), 149 | float64(pos.Z()), 150 | }) 151 | case *packet.MovePlayer: 152 | pos = pk.Position 153 | renderer.Recenter(mgl64.Vec2{ 154 | float64(pos.X()), 155 | float64(pos.Z()), 156 | }) 157 | case *packet.CommandRequest: 158 | line := strings.Split(pk.CommandLine, " ") 159 | if len(line) == 0 { 160 | continue 161 | } 162 | switch line[0] { 163 | case "/cancel": 164 | _ = conn.WritePacket(&packet.Text{Message: text.Colourf("Terminated save.")}) 165 | continue 166 | case "/reset": 167 | mu.Lock() 168 | for chunkPos := range chunks { 169 | delete(chunks, chunkPos) 170 | } 171 | mu.Unlock() 172 | 173 | renderer.Rerender() 174 | continue 175 | case "/save": 176 | saveName := strings.Join(line[1:], " ") 177 | _ = conn.WritePacket(&packet.Text{Message: text.Colourf("Processing chunks to be saved...")}) 178 | go func() { 179 | prov, err := mcdb.New(saveName, dimension) 180 | if err != nil { 181 | panic(err) 182 | } 183 | for pos, c := range chunks { 184 | c.Compact() 185 | err = prov.SaveChunk(pos, c) 186 | if err != nil { 187 | panic(err) 188 | } 189 | } 190 | prov.SaveSettings(&world.Settings{ 191 | Name: data.WorldName, 192 | Spawn: [3]int{int(pos.X()), int(pos.Y()), int(pos.Z())}, 193 | Time: data.Time, 194 | }) 195 | err = prov.Close() 196 | if err != nil { 197 | panic(err) 198 | } 199 | 200 | _ = conn.WritePacket(&packet.Text{Message: text.Colourf("Saved all chunks received to the \"%v\" folder!", saveName)}) 201 | }() 202 | continue 203 | } 204 | } 205 | if err := serverConn.WritePacket(pk); err != nil { 206 | if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok { 207 | _ = listener.Disconnect(conn, disconnect.Error()) 208 | } 209 | return 210 | } 211 | } 212 | }() 213 | go func() { 214 | defer serverConn.Close() 215 | defer listener.Disconnect(conn, "connection lost") 216 | for { 217 | pk, err := serverConn.ReadPacket() 218 | if err != nil { 219 | if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok { 220 | _ = listener.Disconnect(conn, disconnect.Error()) 221 | } 222 | return 223 | } 224 | switch pk := pk.(type) { 225 | case *packet.AvailableCommands: 226 | pk.Commands = append(pk.Commands, protocol.Command{ 227 | Name: "reset", 228 | Description: text.Colourf("Reset all downloaded chunks"), 229 | Flags: 0x1, 230 | }) 231 | pk.Commands = append(pk.Commands, protocol.Command{ 232 | Name: "save", 233 | Description: text.Colourf("Save all downloaded chunks to a folder"), 234 | Flags: 0x1, 235 | }) 236 | pk.Commands = append(pk.Commands, protocol.Command{ 237 | Name: "cancel", 238 | Description: text.Colourf("Terminate a save-in-progress"), 239 | Flags: 0x1, 240 | }) 241 | case *packet.MovePlayer: 242 | if pk.EntityRuntimeID == data.EntityRuntimeID { 243 | pos = pk.Position 244 | renderer.Recenter(mgl64.Vec2{ 245 | float64(pos.X()), 246 | float64(pos.Z()), 247 | }) 248 | } 249 | case *packet.SubChunk: 250 | go func() { 251 | for _, entry := range pk.SubChunkEntries { 252 | if entry.Result == protocol.SubChunkResultSuccess { 253 | offsetPos := world.ChunkPos{ 254 | pk.Position.X() + int32(entry.Offset[0]), 255 | pk.Position.Z() + int32(entry.Offset[2]), 256 | } 257 | 258 | mu.Lock() 259 | c, ok := chunks[offsetPos] 260 | if !ok { 261 | c = chunk.New(airRID, dimension.Range()) 262 | chunks[offsetPos] = c 263 | } 264 | mu.Unlock() 265 | 266 | var ind byte 267 | newSub, err := chunk.DecodeSubChunk(bytes.NewBuffer(entry.RawPayload), c, &ind, chunk.NetworkEncoding) 268 | if err == nil { 269 | mu.Lock() 270 | c.Sub()[ind] = newSub 271 | mu.Unlock() 272 | } 273 | 274 | renderer.RerenderChunk(offsetPos) 275 | } 276 | } 277 | }() 278 | case *packet.ChangeDimension: 279 | mu.Lock() 280 | for chunkPos := range chunks { 281 | delete(chunks, chunkPos) 282 | } 283 | mu.Unlock() 284 | 285 | dimension = world.Dimension(world.Overworld) 286 | switch pk.Dimension { 287 | case 1: 288 | dimension = world.Nether 289 | case 2: 290 | dimension = world.End 291 | } 292 | 293 | renderer.Rerender() 294 | case *packet.LevelChunk: 295 | switch pk.SubChunkRequestMode { 296 | case protocol.SubChunkRequestModeLegacy: 297 | go func() { 298 | chunkPos := world.ChunkPos{pk.Position.X(), pk.Position.Z()} 299 | c, err := chunk.NetworkDecode(airRID, pk.RawPayload, int(pk.SubChunkCount), oldFormat, dimension.Range()) 300 | if err == nil { 301 | mu.Lock() 302 | chunks[chunkPos] = c 303 | mu.Unlock() 304 | 305 | renderer.RerenderChunk(chunkPos) 306 | } 307 | }() 308 | } 309 | } 310 | if err := conn.WritePacket(pk); err != nil { 311 | return 312 | } 313 | } 314 | }() 315 | } 316 | 317 | type config struct { 318 | Connection struct { 319 | LocalAddress string 320 | RemoteAddress string 321 | } 322 | Downloader struct { 323 | OutputDirectory string 324 | } 325 | } 326 | 327 | // readConfig reads the configuration from the config.toml file, or creates the file if it does not yet exist. 328 | func readConfig() (config, error) { 329 | c := config{} 330 | c.Connection.LocalAddress = ":19132" 331 | c.Connection.RemoteAddress = "play.lbsg.net:19132" 332 | if _, err := os.Stat("config.toml"); os.IsNotExist(err) { 333 | data, err := toml.Marshal(c) 334 | if err != nil { 335 | return c, fmt.Errorf("failed encoding default config: %v", err) 336 | } 337 | if err := os.WriteFile("config.toml", data, 0644); err != nil { 338 | return c, fmt.Errorf("failed creating config: %v", err) 339 | } 340 | return c, nil 341 | } 342 | data, err := os.ReadFile("config.toml") 343 | if err != nil { 344 | return c, fmt.Errorf("error reading config: %v", err) 345 | } 346 | if err := toml.Unmarshal(data, &c); err != nil { 347 | return c, fmt.Errorf("error decoding config: %v", err) 348 | } 349 | return c, nil 350 | } 351 | 352 | // tokenSource returns a token source for using with a gophertunnel client. It either reads it from the 353 | // token.tok file if cached or requests logging in with a device code. 354 | func tokenSource() oauth2.TokenSource { 355 | check := func(err error) { 356 | if err != nil { 357 | panic(err) 358 | } 359 | } 360 | token := new(oauth2.Token) 361 | tokenData, err := ioutil.ReadFile("token.tok") 362 | if err == nil { 363 | _ = json.Unmarshal(tokenData, token) 364 | } else { 365 | token, err = auth.RequestLiveToken() 366 | check(err) 367 | } 368 | src := auth.RefreshTokenSource(token) 369 | _, err = src.Token() 370 | if err != nil { 371 | token, err = auth.RequestLiveToken() 372 | check(err) 373 | src = auth.RefreshTokenSource(token) 374 | } 375 | tok, _ := src.Token() 376 | b, _ := json.Marshal(tok) 377 | _ = ioutil.WriteFile("token.tok", b, 0644) 378 | return src 379 | } 380 | -------------------------------------------------------------------------------- /dragonfly/mcdb/provider.go: -------------------------------------------------------------------------------- 1 | package mcdb 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/df-mc/goleveldb/leveldb" 8 | "github.com/df-mc/goleveldb/leveldb/opt" 9 | "github.com/justtaldevelops/worldcompute/dragonfly/chunk" 10 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 11 | "github.com/justtaldevelops/worldcompute/dragonfly/world" 12 | "github.com/sandertv/gophertunnel/minecraft/nbt" 13 | "github.com/sandertv/gophertunnel/minecraft/protocol" 14 | "io/ioutil" 15 | "math" 16 | "os" 17 | "path/filepath" 18 | "time" 19 | ) 20 | 21 | // Provider implements a world provider for the Minecraft world format, which is based on a leveldb database. 22 | type Provider struct { 23 | db *leveldb.DB 24 | dim world.Dimension 25 | dir string 26 | d data 27 | } 28 | 29 | // chunkVersion is the current version of chunks. 30 | const chunkVersion = 27 31 | 32 | // New creates a new provider reading and writing from/to files under the path passed. If a world is present 33 | // at the path, New will parse its data and initialise the world with it. If the data cannot be parsed, an 34 | // error is returned. 35 | func New(dir string, d world.Dimension) (*Provider, error) { 36 | _ = os.MkdirAll(filepath.Join(dir, "db"), 0777) 37 | 38 | p := &Provider{dir: dir, dim: d} 39 | if _, err := os.Stat(filepath.Join(dir, "level.dat")); os.IsNotExist(err) { 40 | // A level.dat was not currently present for the world. 41 | p.initDefaultLevelDat() 42 | } else { 43 | f, err := ioutil.ReadFile(filepath.Join(dir, "level.dat")) 44 | if err != nil { 45 | return nil, fmt.Errorf("error opening level.dat file: %w", err) 46 | } 47 | // The first 8 bytes are a useless header (version and length): We don't need it. 48 | if len(f) < 8 { 49 | // The file did not have enough content, meaning it is corrupted. We return an error. 50 | return nil, fmt.Errorf("level.dat exists but has no data") 51 | } 52 | if err := nbt.UnmarshalEncoding(f[8:], &p.d, nbt.LittleEndian); err != nil { 53 | return nil, fmt.Errorf("error decoding level.dat NBT: %w", err) 54 | } 55 | p.d.WorldStartCount++ 56 | } 57 | db, ok := cacheLoad(dir) 58 | if !ok { 59 | var err error 60 | if db, err = leveldb.OpenFile(filepath.Join(dir, "db"), &opt.Options{ 61 | Compression: opt.FlateCompression, 62 | BlockSize: 16 * opt.KiB, 63 | }); err != nil { 64 | return nil, fmt.Errorf("error opening leveldb database: %w", err) 65 | } 66 | cacheStore(dir, db) 67 | } 68 | 69 | p.db = db 70 | return p, nil 71 | } 72 | 73 | // initDefaultLevelDat initialises a default level.dat file. 74 | func (p *Provider) initDefaultLevelDat() { 75 | p.d.DoDayLightCycle = true 76 | p.d.DoWeatherCycle = true 77 | p.d.BaseGameVersion = protocol.CurrentVersion 78 | p.d.NetworkVersion = protocol.CurrentProtocol 79 | p.d.LastOpenedWithVersion = minimumCompatibleClientVersion 80 | p.d.MinimumCompatibleClientVersion = minimumCompatibleClientVersion 81 | p.d.LevelName = "World" 82 | p.d.GameType = 1 83 | p.d.StorageVersion = 8 84 | p.d.Generator = 2 85 | p.d.Abilities.WalkSpeed = 0.1 86 | p.d.PVP = true 87 | p.d.WorldStartCount = 1 88 | p.d.RandomTickSpeed = 1 89 | p.d.FallDamage = true 90 | p.d.FireDamage = true 91 | p.d.DrowningDamage = true 92 | p.d.CommandsEnabled = true 93 | p.d.MultiPlayerGame = true 94 | p.d.SpawnY = math.MaxInt32 95 | p.d.Difficulty = 2 96 | p.d.DoWeatherCycle = true 97 | p.d.RainLevel = 1.0 98 | p.d.LightningLevel = 1.0 99 | p.d.ServerChunkTickRange = 6 100 | p.d.NetherScale = 8 101 | } 102 | 103 | // Settings returns the world.Settings of the world loaded by the Provider. 104 | func (p *Provider) Settings(s *world.Settings) { 105 | s.Name = p.d.LevelName 106 | s.Spawn = cube.Pos{int(p.d.SpawnX), int(p.d.SpawnY), int(p.d.SpawnZ)} 107 | s.Time = p.d.Time 108 | s.TimeCycle = p.d.DoDayLightCycle 109 | s.WeatherCycle = p.d.DoWeatherCycle 110 | s.RainTime = int64(p.d.RainTime) 111 | s.Raining = p.d.RainLevel > 0 112 | s.ThunderTime = int64(p.d.LightningTime) 113 | s.Thundering = p.d.LightningLevel > 0 114 | s.CurrentTick = p.d.CurrentTick 115 | s.DefaultGameMode = p.loadDefaultGameMode() 116 | s.Difficulty = p.loadDifficulty() 117 | s.TickRange = p.d.ServerChunkTickRange 118 | } 119 | 120 | // SaveSettings saves the world.Settings passed to the level.dat. 121 | func (p *Provider) SaveSettings(s *world.Settings) { 122 | p.d.LevelName = s.Name 123 | p.d.SpawnX, p.d.SpawnY, p.d.SpawnZ = int32(s.Spawn.X()), int32(s.Spawn.Y()), int32(s.Spawn.Z()) 124 | p.d.Time = s.Time 125 | p.d.DoDayLightCycle = s.TimeCycle 126 | p.d.DoWeatherCycle = s.WeatherCycle 127 | p.d.RainTime, p.d.RainLevel = int32(s.RainTime), 0 128 | p.d.LightningTime, p.d.LightningLevel = int32(s.ThunderTime), 0 129 | if s.Raining { 130 | p.d.RainLevel = 1 131 | } 132 | if s.Thundering { 133 | p.d.LightningLevel = 1 134 | } 135 | p.d.CurrentTick = s.CurrentTick 136 | p.d.ServerChunkTickRange = s.TickRange 137 | p.saveDefaultGameMode(s.DefaultGameMode) 138 | p.saveDifficulty(s.Difficulty) 139 | } 140 | 141 | // LoadChunk loads a chunk at the position passed from the leveldb database. If it doesn't exist, exists is 142 | // false. If an error is returned, exists is always assumed to be true. 143 | func (p *Provider) LoadChunk(position world.ChunkPos) (c *chunk.Chunk, exists bool, err error) { 144 | data := chunk.SerialisedData{} 145 | key := p.index(position) 146 | 147 | // This key is where the version of a chunk resides. The chunk version has changed many times, without any 148 | // actual substantial changes, so we don't check this. 149 | _, err = p.db.Get(append(key, keyVersion), nil) 150 | if err == leveldb.ErrNotFound { 151 | // The new key was not found, so we try the old key. 152 | if _, err = p.db.Get(append(key, keyVersionOld), nil); err != nil { 153 | return nil, false, nil 154 | } 155 | } else if err != nil { 156 | return nil, true, fmt.Errorf("error reading version: %w", err) 157 | } 158 | 159 | data.Biomes, err = p.db.Get(append(key, key3DData), nil) 160 | if err != nil && err != leveldb.ErrNotFound { 161 | return nil, false, fmt.Errorf("error reading 3D data: %w", err) 162 | } 163 | if len(data.Biomes) > 512 { 164 | // Strip the heightmap from the biomes. 165 | data.Biomes = data.Biomes[512:] 166 | } 167 | 168 | data.BlockNBT, err = p.db.Get(append(key, keyBlockEntities), nil) 169 | // Block entities aren't present when there aren't any, so it's okay if we can't find the key. 170 | if err != nil && err != leveldb.ErrNotFound { 171 | return nil, true, fmt.Errorf("error reading block entities: %w", err) 172 | } 173 | data.SubChunks = make([][]byte, (p.dim.Range().Height()>>4)+1) 174 | for i := range data.SubChunks { 175 | data.SubChunks[i], err = p.db.Get(append(key, keySubChunkData, uint8(i+(p.dim.Range()[0]>>4))), nil) 176 | if err == leveldb.ErrNotFound { 177 | // No sub chunk present at this Y level. We skip this one and move to the next, which might still 178 | // be present. 179 | continue 180 | } else if err != nil { 181 | return nil, true, fmt.Errorf("error reading sub chunk data %v: %w", i, err) 182 | } 183 | } 184 | c, err = chunk.DiskDecode(data, p.dim.Range()) 185 | return c, true, err 186 | } 187 | 188 | // SaveChunk saves a chunk at the position passed to the leveldb database. Its version is written as the 189 | // version in the chunkVersion constant. 190 | func (p *Provider) SaveChunk(position world.ChunkPos, c *chunk.Chunk) error { 191 | data := chunk.Encode(c, chunk.DiskEncoding) 192 | 193 | key := p.index(position) 194 | _ = p.db.Put(append(key, keyVersion), []byte{chunkVersion}, nil) 195 | // Write the heightmap by just writing 512 empty bytes. 196 | _ = p.db.Put(append(key, key3DData), append(make([]byte, 512), data.Biomes...), nil) 197 | 198 | finalisation := make([]byte, 4) 199 | binary.LittleEndian.PutUint32(finalisation, 2) 200 | _ = p.db.Put(append(key, keyFinalisation), finalisation, nil) 201 | 202 | for i, sub := range data.SubChunks { 203 | _ = p.db.Put(append(key, keySubChunkData, byte(i+(c.Range()[0]>>4))), sub, nil) 204 | } 205 | return nil 206 | } 207 | 208 | // loadDefaultGameMode returns the default game mode stored in the level.dat. 209 | func (p *Provider) loadDefaultGameMode() world.GameMode { 210 | switch p.d.GameType { 211 | default: 212 | return world.GameModeSurvival 213 | case 1: 214 | return world.GameModeCreative 215 | case 2: 216 | return world.GameModeAdventure 217 | case 3: 218 | return world.GameModeSpectator 219 | } 220 | } 221 | 222 | // saveDefaultGameMode changes the default game mode in the level.dat. 223 | func (p *Provider) saveDefaultGameMode(mode world.GameMode) { 224 | switch mode { 225 | case world.GameModeSurvival: 226 | p.d.GameType = 0 227 | case world.GameModeCreative: 228 | p.d.GameType = 1 229 | case world.GameModeAdventure: 230 | p.d.GameType = 2 231 | case world.GameModeSpectator: 232 | p.d.GameType = 3 233 | } 234 | } 235 | 236 | // loadDifficulty loads the difficulty stored in the level.dat. 237 | func (p *Provider) loadDifficulty() world.Difficulty { 238 | switch p.d.Difficulty { 239 | default: 240 | return world.DifficultyNormal 241 | case 0: 242 | return world.DifficultyPeaceful 243 | case 1: 244 | return world.DifficultyEasy 245 | case 3: 246 | return world.DifficultyHard 247 | } 248 | } 249 | 250 | // saveDifficulty saves the difficulty passed to the level.dat. 251 | func (p *Provider) saveDifficulty(d world.Difficulty) { 252 | switch d { 253 | case world.DifficultyPeaceful: 254 | p.d.Difficulty = 0 255 | case world.DifficultyEasy: 256 | p.d.Difficulty = 1 257 | case world.DifficultyNormal: 258 | p.d.Difficulty = 2 259 | case world.DifficultyHard: 260 | p.d.Difficulty = 3 261 | } 262 | } 263 | 264 | // LoadBlockNBT loads all block entities from the chunk position passed. 265 | func (p *Provider) LoadBlockNBT(position world.ChunkPos) ([]map[string]interface{}, error) { 266 | data, err := p.db.Get(append(p.index(position), keyBlockEntities), nil) 267 | if err != leveldb.ErrNotFound && err != nil { 268 | return nil, err 269 | } 270 | var a []map[string]interface{} 271 | 272 | buf := bytes.NewBuffer(data) 273 | dec := nbt.NewDecoderWithEncoding(buf, nbt.LittleEndian) 274 | 275 | for buf.Len() != 0 { 276 | var m map[string]interface{} 277 | if err := dec.Decode(&m); err != nil { 278 | return nil, fmt.Errorf("error decoding block NBT: %w", err) 279 | } 280 | a = append(a, m) 281 | } 282 | return a, nil 283 | } 284 | 285 | // SaveBlockNBT saves all block NBT data to the chunk position passed. 286 | func (p *Provider) SaveBlockNBT(position world.ChunkPos, data []map[string]interface{}) error { 287 | if len(data) == 0 { 288 | return p.db.Delete(append(p.index(position), keyBlockEntities), nil) 289 | } 290 | buf := bytes.NewBuffer(nil) 291 | enc := nbt.NewEncoderWithEncoding(buf, nbt.LittleEndian) 292 | for _, d := range data { 293 | if err := enc.Encode(d); err != nil { 294 | return fmt.Errorf("error encoding block NBT: %w", err) 295 | } 296 | } 297 | return p.db.Put(append(p.index(position), keyBlockEntities), buf.Bytes(), nil) 298 | } 299 | 300 | // Close closes the provider, saving any file that might need to be saved, such as the level.dat. 301 | func (p *Provider) Close() error { 302 | p.d.LastPlayed = time.Now().Unix() 303 | if cacheDelete(p.dir) != 0 { 304 | // The same provider is still alive elsewhere. Don't store the data to the level.dat and levelname.txt just yet. 305 | return nil 306 | } 307 | f, err := os.OpenFile(filepath.Join(p.dir, "level.dat"), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) 308 | if err != nil { 309 | return fmt.Errorf("error opening level.dat file: %w", err) 310 | } 311 | 312 | buf := bytes.NewBuffer(nil) 313 | _ = binary.Write(buf, binary.LittleEndian, int32(3)) 314 | nbtData, err := nbt.MarshalEncoding(p.d, nbt.LittleEndian) 315 | if err != nil { 316 | return fmt.Errorf("error encoding level.dat to NBT: %w", err) 317 | } 318 | _ = binary.Write(buf, binary.LittleEndian, int32(len(nbtData))) 319 | _, _ = buf.Write(nbtData) 320 | 321 | _, _ = f.Write(buf.Bytes()) 322 | 323 | if err := f.Close(); err != nil { 324 | return fmt.Errorf("error closing level.dat: %w", err) 325 | } 326 | //noinspection SpellCheckingInspection 327 | if err := ioutil.WriteFile(filepath.Join(p.dir, "levelname.txt"), []byte(p.d.LevelName), 0644); err != nil { 328 | return fmt.Errorf("error writing levelname.txt: %w", err) 329 | } 330 | return p.db.Close() 331 | } 332 | 333 | // index returns a byte buffer holding the written index of the chunk position passed. If the dimension passed to New 334 | // is not world.Overworld, the length of the index returned is 12. It is 8 otherwise. 335 | func (p *Provider) index(position world.ChunkPos) []byte { 336 | x, z, dim := uint32(position[0]), uint32(position[1]), uint32(p.dim.EncodeDimension()) 337 | b := make([]byte, 12) 338 | 339 | binary.LittleEndian.PutUint32(b, x) 340 | binary.LittleEndian.PutUint32(b[4:], z) 341 | if dim == 0 { 342 | return b[:8] 343 | } 344 | binary.LittleEndian.PutUint32(b[8:], dim) 345 | return b 346 | } 347 | -------------------------------------------------------------------------------- /dragonfly/chunk/light.go: -------------------------------------------------------------------------------- 1 | package chunk 2 | 3 | import ( 4 | "bytes" 5 | "container/list" 6 | "github.com/justtaldevelops/worldcompute/dragonfly/cube" 7 | ) 8 | 9 | // SkyLight holds a light implementation that can be used for propagating skylight through a sub chunk. 10 | var SkyLight skyLight 11 | 12 | // BlockLight holds a light implementation that can be used for propagating block light through a sub chunk. 13 | var BlockLight blockLight 14 | 15 | // light is a type that can be used to set and retrieve light of a specific type in a sub chunk. 16 | type light interface { 17 | light(sub *SubChunk, x, y, z uint8) uint8 18 | setLight(sub *SubChunk, x, y, z, v uint8) 19 | } 20 | 21 | type skyLight struct{} 22 | 23 | func (skyLight) light(sub *SubChunk, x, y, z uint8) uint8 { return sub.SkyLight(x, y, z) } 24 | func (skyLight) setLight(sub *SubChunk, x, y, z, v uint8) { sub.SetSkyLight(x, y, z, v) } 25 | 26 | type blockLight struct{} 27 | 28 | func (blockLight) light(sub *SubChunk, x, y, z uint8) uint8 { return sub.BlockLight(x, y, z) } 29 | func (blockLight) setLight(sub *SubChunk, x, y, z, v uint8) { sub.SetBlockLight(x, y, z, v) } 30 | 31 | // FillLight executes the light 'filling' stage, where the chunk is filled with light coming only from the 32 | // chunk itself, without light crossing chunk borders. 33 | func FillLight(c *Chunk) { 34 | initialiseLightSlices(c) 35 | queue := list.New() 36 | 37 | insertBlockLightNodes(queue, c) 38 | for queue.Len() != 0 { 39 | fillPropagate(queue, c, BlockLight) 40 | } 41 | insertSkyLightNodes(queue, c) 42 | for queue.Len() != 0 { 43 | fillPropagate(queue, c, SkyLight) 44 | } 45 | } 46 | 47 | // SpreadLight executes the light 'spreading' stage, where the chunk has its light spread into the 48 | // neighbouring chunks. The neighbouring chunks must have passed the light 'filling' stage before this 49 | // function is called for a chunk. 50 | func SpreadLight(c *Chunk, neighbours []*Chunk) { 51 | queue := list.New() 52 | spreadLight(c, neighbours, queue, BlockLight) 53 | spreadLight(c, neighbours, queue, SkyLight) 54 | } 55 | 56 | // spreadLight spreads the light from Chunk c into its neighbours. The nodes are added to the list.List passed. 57 | func spreadLight(c *Chunk, neighbours []*Chunk, queue *list.List, lt light) { 58 | insertLightSpreadingNodes(queue, c, neighbours, lt) 59 | for queue.Len() != 0 { 60 | spreadPropagate(queue, c, neighbours, lt) 61 | } 62 | } 63 | 64 | var ( 65 | fullLight = bytes.Repeat([]byte{0xff}, 2048) 66 | fullLightPtr = &fullLight[0] 67 | noLight = make([]uint8, 2048) 68 | noLightPtr = &noLight[0] 69 | ) 70 | 71 | // initialiseLightSlices initialises the light slices in all SubChunks held by the Chunk. 72 | func initialiseLightSlices(c *Chunk) { 73 | index := len(c.sub) - 1 74 | for index >= 0 { 75 | if sub := c.sub[index]; sub.Empty() { 76 | sub.skyLight = fullLight 77 | sub.blockLight = noLight 78 | index-- 79 | continue 80 | } 81 | // We've hit the topmost empty SubChunk. 82 | break 83 | } 84 | for index >= 0 { 85 | c.sub[index].skyLight = noLight 86 | c.sub[index].blockLight = noLight 87 | index-- 88 | } 89 | } 90 | 91 | // insertBlockLightNodes iterates over the chunk and looks for blocks that have a light level of at least 1. 92 | // If one is found, a node is added for it to the node queue. 93 | func insertBlockLightNodes(queue *list.List, c *Chunk) { 94 | for index, sub := range c.sub { 95 | // Potential fast path out: We first check the palette to see if there are any blocks that emit light in the 96 | // block storage. If not, we don't need to iterate the full storage. 97 | if !anyBlockLight(sub) { 98 | continue 99 | } 100 | baseY := c.SubY(int16(index)) 101 | for y := uint8(0); y < 16; y++ { 102 | actualY := int16(y) + baseY 103 | for x := uint8(0); x < 16; x++ { 104 | for z := uint8(0); z < 16; z++ { 105 | if level := highestEmissionLevel(sub, x, y, z); level > 0 { 106 | queue.PushBack(lightNode{x: int8(x), z: int8(z), y: actualY, level: level}) 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | // anyBlockLight checks if there are any blocks in the SubChunk passed that emit light. 115 | func anyBlockLight(sub *SubChunk) bool { 116 | for _, layer := range sub.storages { 117 | for _, id := range layer.palette.values { 118 | if LightBlocks[id] != 0 { 119 | return true 120 | } 121 | } 122 | } 123 | return false 124 | } 125 | 126 | // insertSkyLightNodes iterates over the chunk and inserts a light node anywhere at the highest block in the 127 | // chunk. In addition, any skylight above those nodes will be set to 15. 128 | func insertSkyLightNodes(queue *list.List, c *Chunk) { 129 | m := calculateHeightmap(c) 130 | highestY := int16(c.r[0]) 131 | for index := range c.sub { 132 | if c.sub[index] != nil { 133 | highestY = c.SubY(int16(index)) + 15 134 | } 135 | } 136 | for x := uint8(0); x < 16; x++ { 137 | for z := uint8(0); z < 16; z++ { 138 | current := m.at(x, z) 139 | highestNeighbour := current 140 | 141 | if x != 15 { 142 | if val := m.at(x+1, z); val > highestNeighbour { 143 | highestNeighbour = val 144 | } 145 | } 146 | if x != 0 { 147 | if val := m.at(x-1, z); val > highestNeighbour { 148 | highestNeighbour = val 149 | } 150 | } 151 | if z != 15 { 152 | if val := m.at(x, z+1); val > highestNeighbour { 153 | highestNeighbour = val 154 | } 155 | } 156 | if z != 0 { 157 | if val := m.at(x, z-1); val > highestNeighbour { 158 | highestNeighbour = val 159 | } 160 | } 161 | 162 | // We can do a bit of an optimisation here: We don't need to insert nodes if the neighbours are 163 | // lower than the current one, on the same index level, or one level higher, because light in this 164 | // column can't spread below that anyway. 165 | for y := current; y < highestY; y++ { 166 | if y == current { 167 | level := filterLevel(c.SubChunk(y), x, uint8(y&0xf), z) 168 | if level < 14 && level > 0 { 169 | // If we hit a block like water or leaves, we need a node above this block regardless 170 | // of the neighbours. 171 | queue.PushBack(lightNode{x: int8(x), z: int8(z), y: y + 1, level: 15}) 172 | continue 173 | } 174 | } 175 | if y < highestNeighbour-1 { 176 | queue.PushBack(lightNode{x: int8(x), z: int8(z), y: y + 1, level: 15}) 177 | continue 178 | } 179 | // Fill the rest of the column with skylight on full strength. 180 | c.SubChunk(y+1).SetSkyLight(x, uint8((y+1)&0xf), z, 15) 181 | } 182 | } 183 | } 184 | } 185 | 186 | // insertLightSpreadingNodes inserts light nodes into the node queue passed which, when propagated, will 187 | // spread into the neighbouring chunks. 188 | func insertLightSpreadingNodes(queue *list.List, c *Chunk, neighbours []*Chunk, lt light) { 189 | for index, sub := range c.sub { 190 | baseY := c.SubY(int16(index)) 191 | for y := uint8(0); y < 16; y++ { 192 | totalY := int16(y) + baseY 193 | for x := uint8(0); x < 16; x++ { 194 | for z := uint8(0); z < 16; z++ { 195 | if z != 0 && z != 15 && x != 0 && x != 15 { 196 | break 197 | } 198 | l := lt.light(sub, x, y, z) 199 | if l <= 1 { 200 | // The light level was either 0 or 1, meaning it cannot propagate either way. 201 | continue 202 | } 203 | nodeNeeded := false 204 | if x == 0 { 205 | subNeighbour := neighbours[1].sub[index] 206 | if subNeighbour != nil && lt.light(subNeighbour, 15, y, z) < l { 207 | nodeNeeded = true 208 | } 209 | } else if x == 15 { 210 | subNeighbour := neighbours[6].sub[index] 211 | if subNeighbour != nil && lt.light(subNeighbour, 0, y, z) < l { 212 | nodeNeeded = true 213 | } 214 | } 215 | if !nodeNeeded { 216 | if z == 0 { 217 | subNeighbour := neighbours[3].sub[index] 218 | if subNeighbour != nil && lt.light(subNeighbour, x, y, 15) < l { 219 | nodeNeeded = true 220 | } 221 | } else if z == 15 { 222 | subNeighbour := neighbours[4].sub[index] 223 | if subNeighbour != nil && lt.light(subNeighbour, x, y, 0) < l { 224 | nodeNeeded = true 225 | } 226 | } 227 | } 228 | if nodeNeeded { 229 | queue.PushBack(lightNode{x: int8(x), y: totalY, z: int8(z), level: l, first: true}) 230 | } 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | // spreadPropagate propagates a skylight node in the queue past through the chunk passed and its neighbours, 238 | // unlike fillPropagate, which only propagates within the chunk. 239 | func spreadPropagate(queue *list.List, c *Chunk, neighbourChunks []*Chunk, lt light) { 240 | node := queue.Remove(queue.Front()).(lightNode) 241 | 242 | x, y, z := uint8(node.x&0xf), node.y, uint8(node.z&0xf) 243 | yLocal := uint8(y & 0xf) 244 | sub := chunkByNode(node, c, neighbourChunks).SubChunk(y) 245 | 246 | if !node.first { 247 | filter := filterLevel(sub, x, yLocal, z) + 1 248 | if filter >= node.level { 249 | return 250 | } 251 | node.level -= filter 252 | if lt.light(sub, x, yLocal, z) >= node.level { 253 | // This neighbour already had either as high of a level as what we're updating it to, or 254 | // higher already, so spreading it further is pointless as that will already have been done. 255 | return 256 | } 257 | lt.setLight(sub, x, yLocal, z, node.level) 258 | } 259 | for _, neighbour := range node.neighbours(c.r) { 260 | neighbour.level = node.level 261 | queue.PushBack(neighbour) 262 | } 263 | } 264 | 265 | // fillPropagate propagates a skylight node in the node queue passed within the chunk itself. It does not 266 | // spread the light beyond the chunk. 267 | func fillPropagate(queue *list.List, c *Chunk, lt light) { 268 | node := queue.Remove(queue.Front()).(lightNode) 269 | 270 | x, y, z := uint8(node.x), node.y, uint8(node.z) 271 | yLocal := uint8(y & 0xf) 272 | sub := c.SubChunk(y) 273 | 274 | if lt.light(sub, x, yLocal, z) >= node.level { 275 | // This neighbour already had either as high of a level as what we're updating it to, or 276 | // higher already, so spreading it further is pointless as that will already have been done. 277 | return 278 | } 279 | lt.setLight(sub, x, yLocal, z, node.level) 280 | 281 | // If the level is 1 or lower, it won't be able to propagate any further. 282 | if node.level > 1 { 283 | for _, neighbour := range node.neighbours(c.r) { 284 | if neighbour.x < 0 || neighbour.x > 15 || neighbour.z < 0 || neighbour.z > 15 { 285 | // In the fill stage, we don't propagate skylight out of the chunk. 286 | continue 287 | } 288 | sub := filterLevel(c.SubChunk(neighbour.y), uint8(neighbour.x), uint8(neighbour.y&0xf), uint8(neighbour.z)) + 1 289 | if sub >= node.level { 290 | // No light left to propagate. 291 | continue 292 | } 293 | neighbour.level = node.level - sub 294 | queue.PushBack(neighbour) 295 | } 296 | } 297 | } 298 | 299 | // LightBlocks is a list of block light levels (0-15) indexed by block runtime IDs. The map is used to do a 300 | // fast lookup of block light. 301 | var LightBlocks = make([]uint8, 0, 7000) 302 | 303 | // FilteringBlocks is a map for checking if a block runtime ID filters light, and if so, how many levels. 304 | // Light is able to propagate through these blocks, but will have its level reduced. 305 | var FilteringBlocks = make([]uint8, 0, 7000) 306 | 307 | // lightNode is a node pushed to the queue which is used to propagate light. 308 | type lightNode struct { 309 | x, z int8 310 | y int16 311 | level uint8 312 | first bool 313 | } 314 | 315 | // neighbours returns all neighbouring nodes of the current one. 316 | func (n lightNode) neighbours(r cube.Range) []lightNode { 317 | neighbours := make([]lightNode, 6) 318 | neighbours[0] = lightNode{x: n.x - 1, y: n.y, z: n.z} 319 | neighbours[1] = lightNode{x: n.x + 1, y: n.y, z: n.z} 320 | neighbours[2] = lightNode{x: n.x, y: n.y, z: n.z - 1} 321 | neighbours[3] = lightNode{x: n.x, y: n.y, z: n.z + 1} 322 | 323 | if n.y == int16(r[1]) { 324 | neighbours[4] = lightNode{x: n.x, y: n.y - 1, z: n.z} 325 | return neighbours[:5] 326 | } else if n.y == int16(r[0]) { 327 | neighbours[4] = lightNode{x: n.x, y: n.y + 1, z: n.z} 328 | return neighbours[:5] 329 | } 330 | neighbours[4] = lightNode{x: n.x, y: n.y + 1, z: n.z} 331 | neighbours[5] = lightNode{x: n.x, y: n.y - 1, z: n.z} 332 | 333 | return neighbours 334 | } 335 | 336 | // chunkByNode selects a chunk (either the centre or one of the neighbours) depending on the position of the 337 | // node passed. 338 | func chunkByNode(node lightNode, centre *Chunk, neighbours []*Chunk) *Chunk { 339 | switch { 340 | case node.x < 0 && node.z < 0: 341 | return neighbours[0] 342 | case node.x < 0 && node.z >= 0 && node.z <= 15: 343 | return neighbours[1] 344 | case node.x < 0 && node.z >= 16: 345 | return neighbours[2] 346 | case node.x >= 0 && node.x <= 15 && node.z < 0: 347 | return neighbours[3] 348 | case node.x >= 0 && node.x <= 15 && node.z >= 0 && node.z <= 15: 349 | return centre 350 | case node.x >= 0 && node.x <= 15 && node.z >= 16: 351 | return neighbours[4] 352 | case node.x >= 16 && node.z < 0: 353 | return neighbours[5] 354 | case node.x >= 16 && node.z >= 0 && node.z <= 15: 355 | return neighbours[6] 356 | case node.x >= 16 && node.z >= 16: 357 | return neighbours[7] 358 | } 359 | panic("should never happen") 360 | } 361 | 362 | // highestEmissionLevel checks for the block with the highest emission level at a position and returns it. 363 | func highestEmissionLevel(sub *SubChunk, x, y, z uint8) uint8 { 364 | storages := sub.storages 365 | // We offer several fast ways out to get a little more performance out of this. 366 | switch len(storages) { 367 | case 0: 368 | return 0 369 | case 1: 370 | id := storages[0].At(x, y, z) 371 | if id == sub.air { 372 | return 0 373 | } 374 | return LightBlocks[id] 375 | case 2: 376 | var highest uint8 377 | id := storages[0].At(x, y, z) 378 | if id != sub.air { 379 | highest = LightBlocks[id] 380 | } 381 | id = storages[1].At(x, y, z) 382 | if id != sub.air { 383 | if v := LightBlocks[id]; v > highest { 384 | highest = v 385 | } 386 | } 387 | return highest 388 | } 389 | var highest uint8 390 | for i := range storages { 391 | if l := LightBlocks[storages[i].At(x, y, z)]; l > highest { 392 | highest = l 393 | } 394 | } 395 | return highest 396 | } 397 | 398 | // filterLevel checks for the block with the highest filter level in the sub chunk at a specific position, 399 | // returning 15 if there is a block, but if it is not present in the FilteringBlocks map. 400 | func filterLevel(sub *SubChunk, x, y, z uint8) uint8 { 401 | storages := sub.storages 402 | // We offer several fast ways out to get a little more performance out of this. 403 | switch len(storages) { 404 | case 0: 405 | return 0 406 | case 1: 407 | id := storages[0].At(x, y, z) 408 | if id == sub.air { 409 | return 0 410 | } 411 | return FilteringBlocks[id] 412 | case 2: 413 | var highest uint8 414 | 415 | id := storages[0].At(x, y, z) 416 | if id != sub.air { 417 | highest = FilteringBlocks[id] 418 | } 419 | 420 | id = storages[1].At(x, y, z) 421 | if id != sub.air { 422 | if v := FilteringBlocks[id]; v > highest { 423 | highest = v 424 | } 425 | } 426 | return highest 427 | } 428 | var highest uint8 429 | for i := range storages { 430 | id := storages[i].At(x, y, z) 431 | if id != sub.air { 432 | if l := FilteringBlocks[id]; l > highest { 433 | highest = l 434 | } 435 | } 436 | } 437 | return highest 438 | } 439 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 37 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 38 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 39 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 41 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 43 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 44 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 45 | github.com/df-mc/atomic v1.10.0 h1:0ZuxBKwR/hxcFGorKiHIp+hY7hgY+XBTzhCYD2NqSEg= 46 | github.com/df-mc/atomic v1.10.0/go.mod h1:Gw9rf+rPIbydMjA329Jn4yjd/O2c/qusw3iNp4tFGSc= 47 | github.com/df-mc/goleveldb v1.1.9 h1:ihdosZyy5jkQKrxucTQmN90jq/2lUwQnJZjIYIC/9YU= 48 | github.com/df-mc/goleveldb v1.1.9/go.mod h1:+NHCup03Sci5q84APIA21z3iPZCuk6m6ABtg4nANCSk= 49 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 50 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 51 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 52 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 53 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 54 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 55 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 56 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 57 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk= 58 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 59 | github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= 60 | github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 61 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 62 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 63 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 64 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 65 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 66 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 67 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 68 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 69 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 70 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 71 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 72 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 73 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 74 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 75 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 76 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 77 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 78 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 79 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 80 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 81 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 82 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 83 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 84 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 85 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 86 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 87 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 88 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 89 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 90 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 91 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 92 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 93 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 94 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 95 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 96 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 97 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 98 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 99 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 100 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 101 | github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= 102 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 103 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 104 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 105 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 106 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 107 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 108 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 109 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 110 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 111 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 112 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 113 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 114 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 115 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 116 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 117 | github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= 118 | github.com/hajimehoshi/ebiten/v2 v2.2.4 h1:/+qrmbv+W6scgVWwQJ7IyiI2z4y8QM2n0JDHStNC+Ns= 119 | github.com/hajimehoshi/ebiten/v2 v2.2.4/go.mod h1:olKl/qqhMBBAm2oI7Zy292nCtE+nitlmYKNF3UpbFn0= 120 | github.com/hajimehoshi/file2byteslice v0.0.0-20210813153925-5340248a8f41/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= 121 | github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= 122 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= 123 | github.com/hajimehoshi/oto/v2 v2.1.0-alpha.2/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ= 124 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 125 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 126 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 127 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 128 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 129 | github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= 130 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= 131 | github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= 132 | github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII= 133 | github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ= 134 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 135 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 136 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 137 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 138 | github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= 139 | github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 140 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 141 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 142 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 143 | github.com/muhammadmuzzammil1998/jsonc v1.0.0 h1:8o5gBQn4ZA3NBA9DlTujCj2a4w0tqWrPVjDwhzkgTIs= 144 | github.com/muhammadmuzzammil1998/jsonc v1.0.0/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU= 145 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 146 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 147 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 148 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 149 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 150 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 151 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 152 | github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= 153 | github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 154 | github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= 155 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 156 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 157 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 158 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 159 | github.com/sandertv/go-raknet v1.11.1 h1:0auvhHoZNyC/Z1l5xqniE3JE+w3MGO3n3JXEGHzdlRE= 160 | github.com/sandertv/go-raknet v1.11.1/go.mod h1:Gx+WgZBMQ0V2UoouGoJ8Wj6CDrMBQ4SB2F/ggpl5/+Y= 161 | github.com/sandertv/gophertunnel v1.24.0 h1:TPxUJtBHeh3hRNQciAoKFcWa6FB8iyB3FqN8+sx4dh4= 162 | github.com/sandertv/gophertunnel v1.24.0/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= 163 | github.com/sandertv/gophertunnel v1.24.6 h1:GIa6QyRNcyw5E9nm0S1NReEOj0GDk6bS/RHQo8lhkXI= 164 | github.com/sandertv/gophertunnel v1.24.6/go.mod h1:dMOw79FHxr2azEqiGH20AwdljisAN1kqwu5SjPBnZ5k= 165 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 166 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 167 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 168 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 169 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 170 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 171 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 172 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 173 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 174 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 175 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 176 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 177 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 178 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 179 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 180 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 181 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 182 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 183 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 184 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 185 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 186 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 187 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 188 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 189 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= 190 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 191 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 192 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 193 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 194 | golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= 195 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 196 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 197 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 198 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 199 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 200 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 201 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 202 | golang.org/x/exp/shiny v0.0.0-20220318154914-8dddf5d87bd8 h1:EFkPy3Aw0vwtCQVNdU4V0nunADtdtemiTIiz/UMC5rI= 203 | golang.org/x/exp/shiny v0.0.0-20220318154914-8dddf5d87bd8/go.mod h1:NtXcNtv5Wu0zUbBl574y/D5MMZvnQnV3sgjZxbs64Jo= 204 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 205 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 206 | golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 207 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 208 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= 209 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 210 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 211 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 212 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 213 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 214 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 215 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 216 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 217 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 218 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 219 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 220 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 221 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 222 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 223 | golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5 h1:peBP2oZO/xVnGMaWMCyFEI0WENsGj71wx5K12mRELHQ= 224 | golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5/go.mod h1:c4YKU3ZylDmvbw+H/PSvm42vhdWbuxCzbonauEAP9B8= 225 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 226 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 227 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 228 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 229 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 230 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 231 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 232 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 233 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 234 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 238 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 239 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 240 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 242 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 243 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 244 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 246 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 248 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 249 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 250 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 252 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 253 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 254 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 255 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 256 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 257 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 258 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 259 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 260 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 261 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 262 | golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 h1:6mzvA99KwZxbOrxww4EvWVQUnN1+xEu9tafK5ZxkYeA= 263 | golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 264 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 265 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 266 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 267 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 268 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 269 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= 270 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 271 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 272 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 273 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 274 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 279 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 280 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 281 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 282 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 283 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 284 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 285 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 286 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 287 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 312 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 313 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 314 | golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 315 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 316 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 317 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 318 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 319 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 320 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 321 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 322 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 323 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 324 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 325 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 326 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 327 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 328 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 329 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 330 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 331 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 332 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 333 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 334 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 335 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 336 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 337 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 338 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 339 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 340 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 341 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 342 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 343 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 344 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 345 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 346 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 347 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 348 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 349 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 350 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 351 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 352 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 353 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 354 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 355 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 356 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 357 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 358 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 359 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 360 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 361 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 362 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 363 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 364 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 365 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 366 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 367 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 368 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 369 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 370 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 371 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 372 | golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 373 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 374 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 375 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 376 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 377 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 378 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 379 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 380 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 381 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 382 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 383 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 384 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 385 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 386 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 387 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 388 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 389 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 390 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 391 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 392 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 393 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 394 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 395 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 396 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 397 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 398 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 399 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 400 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 401 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 402 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 403 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 404 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 405 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 406 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 407 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 408 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 409 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 410 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 411 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 412 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 413 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 414 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 415 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 416 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 417 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 418 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 419 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 420 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 421 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 422 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 423 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 424 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 425 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 426 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 427 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 428 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 429 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 430 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 431 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 432 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 433 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 434 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 435 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 436 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 437 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 438 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 439 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 440 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 441 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 442 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 443 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 444 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 445 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 446 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 447 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 448 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 449 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 450 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 451 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 452 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 453 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 454 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 455 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 456 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 457 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 458 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 459 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 460 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 461 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 462 | gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= 463 | gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 464 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 465 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 466 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 467 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 468 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 469 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 470 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 471 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 472 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 473 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 474 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 475 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 476 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 477 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 478 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 479 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 480 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 481 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 482 | --------------------------------------------------------------------------------