frameworkServer works perfect now
This commit is contained in:
33857
data/block/block.go
33857
data/block/block.go
File diff suppressed because it is too large
Load Diff
@ -1,152 +0,0 @@
|
||||
//go:build generate
|
||||
// +build generate
|
||||
|
||||
// gen_blocks.go generates block information.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
const (
|
||||
version = "1.17"
|
||||
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/" + version + "/blocks.json"
|
||||
//language=gohtml
|
||||
blockTmpl = `// Code generated by gen_block.go; DO NOT EDIT.
|
||||
|
||||
// Package block stores information about blocks in Minecraft.
|
||||
package block
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// BitsPerBlock indicates how many bits are needed to represent all possible
|
||||
// block states. This value is used to determine the size of the global palette.
|
||||
var BitsPerBlock = bits.Len(uint(len(StateID)))
|
||||
|
||||
// ID describes the numeric ID of a block.
|
||||
type ID uint32
|
||||
|
||||
// Block describes information about a type of block.
|
||||
type Block struct {
|
||||
ID ID
|
||||
DisplayName string
|
||||
Name string
|
||||
|
||||
Hardness float64
|
||||
Diggable bool
|
||||
DropIDs []uint32
|
||||
NeedsTools map[uint32]bool
|
||||
|
||||
MinStateID uint32
|
||||
MaxStateID uint32
|
||||
|
||||
Transparent bool
|
||||
FilterLightLevel int
|
||||
EmitLightLevel int
|
||||
}
|
||||
|
||||
var (
|
||||
{{- range .}}
|
||||
{{.CamelName}} = Block{
|
||||
ID: {{.ID}},
|
||||
DisplayName: "{{.DisplayName}}",
|
||||
Name: "{{.Name}}",
|
||||
Hardness: {{.Hardness}},
|
||||
Diggable: {{.Diggable}},
|
||||
DropIDs: []uint32{ {{- range .DropIDs}}{{.}},{{end -}} },
|
||||
NeedsTools: map[uint32]bool{ {{- range $key, $val := .NeedsTools}}{{$key}}: {{$val}},{{end -}} },
|
||||
MinStateID: {{.MinStateID}},
|
||||
MaxStateID: {{.MaxStateID}},
|
||||
Transparent: {{.Transparent}},
|
||||
FilterLightLevel: {{.FilterLightLevel}},
|
||||
EmitLightLevel: {{.EmitLightLevel}},
|
||||
}{{end}}
|
||||
)
|
||||
|
||||
// ByID is an index of minecraft blocks by their ID.
|
||||
var ByID = map[ID]*Block{ {{range .}}
|
||||
{{.ID}}: &{{.CamelName}},{{end}}
|
||||
}
|
||||
|
||||
// StateID maps all possible state IDs to a corresponding block ID.
|
||||
var StateID = map[uint32]ID{ {{range $block := .}}
|
||||
{{- range .States}}
|
||||
{{.}}: {{$block.ID}},
|
||||
{{- end}}{{end}}
|
||||
}`
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
ID uint32 `json:"id"`
|
||||
CamelName string `json:"-"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Name string `json:"name"`
|
||||
|
||||
Hardness float64 `json:"hardness"`
|
||||
Diggable bool `json:"diggable"`
|
||||
DropIDs []uint32 `json:"drops"`
|
||||
NeedsTools map[uint32]bool `json:"harvestTools"`
|
||||
|
||||
MinStateID uint32 `json:"minStateId"`
|
||||
MaxStateID uint32 `json:"maxStateId"`
|
||||
|
||||
Transparent bool `json:"transparent"`
|
||||
FilterLightLevel int `json:"filterLight"`
|
||||
EmitLightLevel int `json:"emitLight"`
|
||||
}
|
||||
|
||||
func (b Block) States() []uint32 {
|
||||
if b.MinStateID == b.MaxStateID {
|
||||
return []uint32{b.MinStateID}
|
||||
}
|
||||
states := make([]uint32, 0)
|
||||
for i := b.MinStateID; i <= b.MaxStateID; i++ {
|
||||
states = append(states, i)
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
func downloadInfo() ([]*Block, error) {
|
||||
resp, err := http.Get(infoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var data []*Block
|
||||
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, d := range data {
|
||||
d.CamelName = strcase.ToCamel(d.Name)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//go:generate go run $GOFILE
|
||||
//go:generate go fmt block.go
|
||||
func main() {
|
||||
fmt.Println("generating block.go")
|
||||
blocks, err := downloadInfo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f, err := os.Create("block.go")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := template.Must(template.New("").Parse(blockTmpl)).Execute(f, blocks); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,219 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
"github.com/Tnze/go-mc/level"
|
||||
"github.com/Tnze/go-mc/save"
|
||||
"github.com/Tnze/go-mc/save/region"
|
||||
)
|
||||
|
||||
var colors []color.RGBA64
|
||||
var sectionWorkerNum = 1
|
||||
|
||||
var (
|
||||
regionWorkerNum = flag.Int("workers", runtime.NumCPU(), "worker numbers")
|
||||
regionsFold = flag.String("region", filepath.Join(os.Getenv("AppData"), ".minecraft", "saves", "World", "region"), "region directory path")
|
||||
drawBigPicture = flag.Bool("bigmap", true, "draw the big map")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
de, err := os.ReadDir(*regionsFold)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var min, max [2]int
|
||||
updateMinMax := func(pos [2]int) {
|
||||
mkmax(&max[0], &pos[0])
|
||||
mkmax(&max[1], &pos[1])
|
||||
mkmin(&min[0], &pos[0])
|
||||
mkmin(&min[1], &pos[1])
|
||||
}
|
||||
|
||||
if *drawBigPicture {
|
||||
for _, dir := range de {
|
||||
name := dir.Name()
|
||||
var pos [2]int // {x, z}
|
||||
if _, err := fmt.Sscanf(name, "r.%d.%d.mca", &pos[0], &pos[1]); err != nil {
|
||||
log.Printf("Error parsing file name of %s: %v, ignoring", name, err)
|
||||
continue
|
||||
}
|
||||
updateMinMax(pos)
|
||||
}
|
||||
}
|
||||
|
||||
type regions struct {
|
||||
pos [2]int
|
||||
*region.Region
|
||||
}
|
||||
// Open mca files
|
||||
var rs = make(chan regions, *regionWorkerNum)
|
||||
go func() {
|
||||
for _, dir := range de {
|
||||
name := dir.Name()
|
||||
path := filepath.Join(*regionsFold, name)
|
||||
var pos [2]int // {x, z}
|
||||
if _, err := fmt.Sscanf(name, "r.%d.%d.mca", &pos[0], &pos[1]); err != nil {
|
||||
log.Printf("Error parsing file name of %s: %v, ignoring", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
r, err := region.Open(path)
|
||||
if err != nil {
|
||||
log.Printf("Error when opening %s: %v, ignoring", name, err)
|
||||
continue
|
||||
}
|
||||
rs <- regions{pos: pos, Region: r}
|
||||
}
|
||||
close(rs)
|
||||
}()
|
||||
var bigPicture *image.RGBA
|
||||
if *drawBigPicture {
|
||||
bigPicture = image.NewRGBA(image.Rect(min[0]*512, min[1]*512, max[0]*512+512, max[1]*512+512))
|
||||
}
|
||||
var bigWg sync.WaitGroup
|
||||
// draw columns
|
||||
for r := range rs {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 32*16, 32*16))
|
||||
type task struct {
|
||||
data []byte
|
||||
pos [2]int
|
||||
}
|
||||
c := make(chan task)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < *regionWorkerNum; i++ {
|
||||
go func() {
|
||||
var column save.Chunk
|
||||
for task := range c {
|
||||
if err := column.Load(task.data); err != nil {
|
||||
log.Printf("UnmarshalNBT column (%d.%d) error: %v", task.pos[0], task.pos[1], err)
|
||||
}
|
||||
//pos := [2]int{int(column.Level.PosX), int(column.Level.PosZ)}
|
||||
//if pos != task.pos {
|
||||
// fmt.Printf("chunk position not match: want %v, get %v\n", task.pos, pos)
|
||||
//}
|
||||
draw.Draw(
|
||||
img, image.Rect(task.pos[0]*16, task.pos[1]*16, task.pos[0]*16+16, task.pos[1]*16+16),
|
||||
drawColumn(&column), image.Pt(0, 0),
|
||||
draw.Over,
|
||||
)
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for x := 0; x < 32; x++ {
|
||||
for z := 0; z < 32; z++ {
|
||||
if !r.ExistSector(x, z) {
|
||||
continue
|
||||
}
|
||||
data, err := r.ReadSector(x, z)
|
||||
if err != nil {
|
||||
log.Printf("Read sector (%d.%d) error: %v", x, z, err)
|
||||
}
|
||||
wg.Add(1)
|
||||
c <- task{data: data, pos: [2]int{x, z}}
|
||||
}
|
||||
}
|
||||
close(c)
|
||||
wg.Wait()
|
||||
// Save pictures
|
||||
bigWg.Add(1)
|
||||
log.Print("Draw: ", r.pos)
|
||||
go func(img image.Image, pos [2]int) {
|
||||
savePng(img, fmt.Sprintf("r.%d.%d.png", pos[0], pos[1]))
|
||||
if *drawBigPicture {
|
||||
draw.Draw(
|
||||
bigPicture, image.Rect(pos[0]*512, pos[1]*512, pos[0]*512+512, pos[1]*512+512),
|
||||
img, image.Pt(0, 0), draw.Src,
|
||||
)
|
||||
}
|
||||
bigWg.Done()
|
||||
}(img, r.pos)
|
||||
// To close mca files
|
||||
if err := r.Close(); err != nil {
|
||||
log.Printf("Close r.%d.%d.mca error: %v", r.pos[0], r.pos[1], err)
|
||||
}
|
||||
}
|
||||
bigWg.Wait()
|
||||
if *drawBigPicture {
|
||||
savePng(bigPicture, "maps.png")
|
||||
}
|
||||
}
|
||||
|
||||
func drawColumn(column *save.Chunk) (img *image.RGBA) {
|
||||
img = image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
s := column.Sections
|
||||
c := make(chan *save.Section)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < sectionWorkerNum; i++ {
|
||||
go func() {
|
||||
for s := range c {
|
||||
drawSection(s, img)
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
defer close(c)
|
||||
|
||||
wg.Add(len(s))
|
||||
for i := range s {
|
||||
c <- &s[i]
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func drawSection(s *save.Section, img *image.RGBA) {
|
||||
data := *(*[]uint64)((unsafe.Pointer)(&s.BlockStates.Data))
|
||||
palette := s.BlockStates.Palette
|
||||
rawPalette := make([]int, len(palette))
|
||||
for i, v := range palette {
|
||||
// TODO: Consider the properties of block, not only index the block name
|
||||
rawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
|
||||
}
|
||||
c := level.NewStatesPaletteContainerWithData(16*16*16, data, rawPalette)
|
||||
for y := 0; y < 16; y++ {
|
||||
layerImg := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||
for i := 16*16 - 1; i >= 0; i-- {
|
||||
var bid block.ID
|
||||
bid = block.ID(c.Get(i))
|
||||
c := colors[block.ByID[bid].ID]
|
||||
layerImg.Set(i%16, i/16, c)
|
||||
}
|
||||
draw.Draw(
|
||||
img, image.Rect(0, 0, 16, 16),
|
||||
layerImg, image.Pt(0, 0),
|
||||
draw.Over,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: This map should be moved to data/block.
|
||||
var stateIDs map[string]uint32
|
||||
|
||||
func init() {
|
||||
for i, v := range block.StateID {
|
||||
name := block.ByID[v].Name
|
||||
if _, ok := stateIDs[name]; !ok {
|
||||
stateIDs[name] = i
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/gob"
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
"image"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func savePng(img image.Image, name string) {
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := png.Encode(f, img); err != nil {
|
||||
f.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed colors.gob
|
||||
var colorsBin []byte // gob([]color.RGBA64)
|
||||
|
||||
func init() {
|
||||
if err := gob.NewDecoder(bytes.NewReader(colorsBin)).Decode(&colors); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mkmax(c, n *int) {
|
||||
if *c < *n {
|
||||
*c = *n
|
||||
}
|
||||
}
|
||||
func mkmin(c, n *int) {
|
||||
if *c > *n {
|
||||
*c = *n
|
||||
}
|
||||
}
|
||||
|
||||
var idByName = make(map[string]uint32, len(block.ByID))
|
||||
|
||||
func init() {
|
||||
for _, v := range block.ByID {
|
||||
idByName["minecraft:"+v.Name] = uint32(v.ID)
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"compress/zlib"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
)
|
||||
@ -17,21 +18,20 @@ type Block interface {
|
||||
//go:embed block_states.nbt
|
||||
var blockStates []byte
|
||||
|
||||
var toStateID = make(map[Block]int)
|
||||
var toStateID map[Block]int
|
||||
var fromStateID []Block
|
||||
|
||||
// BitsPerBlock indicates how many bits are needed to represent all possible
|
||||
// block states. This value is used to determine the size of the global palette.
|
||||
var BitsPerBlock int
|
||||
|
||||
type State struct {
|
||||
Name string
|
||||
Properties nbt.RawMessage
|
||||
}
|
||||
|
||||
func init() {
|
||||
regState := func(s Block) {
|
||||
if _, ok := toStateID[s]; ok {
|
||||
panic(fmt.Errorf("state %#v already exist", s))
|
||||
}
|
||||
toStateID[s] = len(fromStateID)
|
||||
fromStateID = append(fromStateID, s)
|
||||
}
|
||||
var states []struct {
|
||||
Name string
|
||||
Properties nbt.RawMessage
|
||||
}
|
||||
var states []State
|
||||
// decompress
|
||||
z, err := zlib.NewReader(bytes.NewReader(blockStates))
|
||||
if err != nil {
|
||||
@ -41,6 +41,8 @@ func init() {
|
||||
if _, err = nbt.NewDecoder(z).Decode(&states); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
toStateID = make(map[Block]int, len(states))
|
||||
fromStateID = make([]Block, 0, len(states))
|
||||
for _, state := range states {
|
||||
block := fromID[state.Name]
|
||||
if state.Properties.Type != nbt.TagEnd {
|
||||
@ -49,14 +51,29 @@ func init() {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
regState(block)
|
||||
if _, ok := toStateID[block]; ok {
|
||||
panic(fmt.Errorf("state %#v already exist", block))
|
||||
}
|
||||
toStateID[block] = len(fromStateID)
|
||||
fromStateID = append(fromStateID, block)
|
||||
}
|
||||
BitsPerBlock = bits.Len(uint(len(fromStateID)))
|
||||
}
|
||||
|
||||
func FromStateID(stateID int) Block {
|
||||
return fromStateID[stateID]
|
||||
func FromStateID(stateID int) (b Block, ok bool) {
|
||||
if stateID >= 0 && stateID < len(fromStateID) {
|
||||
b = fromStateID[stateID]
|
||||
ok = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ToStateID(b Block) int {
|
||||
return toStateID[b]
|
||||
func DefaultBlock(id string) (b Block, ok bool) {
|
||||
b, ok = fromID[id]
|
||||
return
|
||||
}
|
||||
|
||||
func ToStateID(b Block) (i int, ok bool) {
|
||||
i, ok = toStateID[b]
|
||||
return
|
||||
}
|
||||
|
@ -2,13 +2,14 @@ package level
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/bits"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
"github.com/Tnze/go-mc/level/block"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/save"
|
||||
)
|
||||
@ -113,11 +114,18 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
|
||||
statePalette := v.BlockStates.Palette
|
||||
stateRawPalette := make([]int, len(statePalette))
|
||||
for i, v := range statePalette {
|
||||
// TODO: Consider the properties of block, not only index the block name
|
||||
stateRawPalette[i] = int(stateIDs[strings.TrimPrefix(v.Name, "minecraft:")])
|
||||
if v.Name != "minecraft:air" {
|
||||
b := v.Block()
|
||||
if b == nil {
|
||||
panic(fmt.Errorf("block not found: %#v", v))
|
||||
}
|
||||
if !isAir(b) {
|
||||
blockCount++
|
||||
}
|
||||
var ok bool
|
||||
stateRawPalette[i], ok = block.ToStateID(b)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("state id not found: %#v", b))
|
||||
}
|
||||
}
|
||||
|
||||
biomesData := *(*[]uint64)((unsafe.Pointer)(&v.Biomes.Data))
|
||||
@ -153,18 +161,6 @@ func ChunkFromSave(c *save.Chunk, secs int) *Chunk {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This map should be moved to data/block.
|
||||
var stateIDs = make(map[string]uint32)
|
||||
|
||||
func init() {
|
||||
for i, v := range block.StateID {
|
||||
name := block.ByID[v].Name
|
||||
if _, ok := stateIDs[name]; !ok {
|
||||
stateIDs[name] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chunk) WriteTo(w io.Writer) (int64, error) {
|
||||
data, err := c.Data()
|
||||
if err != nil {
|
||||
@ -211,8 +207,8 @@ func (s *Section) GetBlock(i int) int {
|
||||
return s.States.Get(i)
|
||||
}
|
||||
func (s *Section) SetBlock(i int, v int) {
|
||||
// TODO: Handle cave air and void air
|
||||
if s.States.Get(i) != 0 {
|
||||
b, _ := block.FromStateID(s.States.Get(i))
|
||||
if isAir(b) {
|
||||
s.blockCount--
|
||||
}
|
||||
if v != 0 {
|
||||
@ -263,3 +259,12 @@ func (l *lightData) WriteTo(w io.Writer) (int64, error) {
|
||||
pk.Array(l.BlockLight),
|
||||
}.WriteTo(w)
|
||||
}
|
||||
|
||||
func isAir(b block.Block) bool {
|
||||
switch b.(type) {
|
||||
case block.Air, block.CaveAir, block.VoidAir:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"errors"
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
"io"
|
||||
|
||||
"github.com/Tnze/go-mc/level/block"
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
)
|
||||
|
||||
// Chunk is 16* chunk
|
||||
@ -45,6 +47,19 @@ type BlockState struct {
|
||||
Properties nbt.RawMessage
|
||||
}
|
||||
|
||||
func (s *BlockState) Block() block.Block {
|
||||
b, ok := block.DefaultBlock(s.Name)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if s.Properties.Type != nbt.TagEnd {
|
||||
if err := s.Properties.Unmarshal(&b); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Load read column data from []byte
|
||||
func (c *Chunk) Load(data []byte) (err error) {
|
||||
var r io.Reader = bytes.NewReader(data[1:])
|
||||
|
Reference in New Issue
Block a user