├── 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 | 
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 |
--------------------------------------------------------------------------------