package server import ( "io" "os" "fmt" "bytes" "bufio" "compress/gzip" "encoding/binary" "git.citrons.xyz/metronode/phony" ) type levelId int32 type blockType byte type levelPlayerId int8 type levelInfo struct { Id levelId Size blockPos IsSpawn bool } type level struct { phony.Inbox levelInfo loadingState int server *Server blocks []byte ids map[levelPlayerId]*player players map[*player]levelPlayerId } const ( levelUnloaded = iota levelLoading levelLoaded ) func newLevel(s *Server, info levelInfo) *level { return &level { levelInfo: info, server: s, loadingState: levelLoaded, blocks: make([]byte, info.Size.X * info.Size.Y * info.Size.Z), ids: make(map[levelPlayerId]*player), players: make(map[*player]levelPlayerId), } } func loadLevel(s *Server, id levelId, isSpawn bool) *level { l := &level { levelInfo: levelInfo {Id: id, IsSpawn: isSpawn}, server: s, ids: make(map[levelPlayerId]*player), players: make(map[*player]levelPlayerId), } l.load() return l } func (l *level) load() { if l.loadingState == levelUnloaded { l.loadingState = levelLoading id := l.Id dataManager.Act(l, func() { f, err := os.Open(fmt.Sprintf("world/level/%d.bin", id)) if l.handleLoadError(err) != nil { return } defer f.Close() var ( rd = bufio.NewReader(f) info levelInfo blocks []byte ) readDataField(rd, &info) z, err := gzip.NewReader(rd) if l.handleLoadError(err) != nil { return } z.Read(make([]byte, 4)) blocks, err = io.ReadAll(z) if l.handleLoadError(err) != nil { return } l.Act(&dataManager, func() { l.loadingState = levelLoaded l.levelInfo = info l.Id = id l.blocks = blocks for player := range l.players { player.OnLevelData(l, l.levelInfo, l.compressLevelData()) } }) }) } } func (l *level) handleLoadError(err error) error { if err == nil { return err } l.Act(&dataManager, func() { l.loadingState = levelUnloaded dataManager.errHand.OnLoadError(l, err) errString := "load level: " + err.Error() if os.IsNotExist(err) { errString = "level not found" } l.LevelError(nil, errString) }) return err } func (l *level) LevelError(from phony.Actor, err string) { l.Act(from, func() { for player := range l.players { player.OnLevelError(l, err, l.levelInfo) } }) } func (l *level) save(done func()) { if done != nil { defer dataManager.Act(nil, done) } if l.loadingState != levelLoaded { return } os.Mkdir("world/level", 0777) f, err := createAtomic(l, fmt.Sprintf("world/level/%d.bin", l.Id)) if err != nil { dataManager.errHand.OnSaveError(l, err) return } defer f.Close() wr := bufio.NewWriter(f) writeDataField(wr, l.levelInfo) data := l.compressLevelData() defer data.Close() io.Copy(wr, data) if wr.Flush() != nil { dataManager.errHand.OnSaveError(l, err) return } } func (l *level) blockIndex(pos blockPos) int { return int(pos.X + pos.Z*l.Size.X + pos.Y*l.Size.X*l.Size.Z) } func (l *level) setBlock(pos blockPos, block blockType) { l.blocks[l.blockIndex(pos)] = byte(block) } func (l *level) getBlock(pos blockPos) blockType { return blockType(l.blocks[l.blockIndex(pos)]) } func (l *level) compressLevelData() io.ReadCloser { rd, wr := io.Pipe() data := bytes.NewReader(bytes.Clone(l.blocks)) go func() { defer wr.Close() z := gzip.NewWriter(wr) defer z.Close() binary.Write(z, binary.BigEndian, uint32(len(l.blocks))) io.Copy(z, data) }() return rd } func (l *level) generateFlat() { var p blockPos for p.Z = 0; p.Z < l.levelInfo.Size.Z; p.Z++ { for p.Y = 0; p.Y < l.levelInfo.Size.Y / 2; p.Y++ { for p.X = 0; p.X < l.levelInfo.Size.X; p.X++ { var block blockType if p.Y == 0 { block = 7 } else if p.Y == l.levelInfo.Size.Y/2 - 1 { block = 2 } else if p.Y > l.levelInfo.Size.Y/2 - 15 { block = 3 } else { block = 1 } l.setBlock(p, block) } } } } func (l *level) Save(from phony.Actor, done func()) { l.Act(from, func() { l.save(func() { if done != nil { from.Act(nil, done) } }) }) } func (l *level) SetBlock(from phony.Actor, pos blockPos, block blockType) { if l.loadingState != levelLoaded { return } l.Act(from, func() { l.setBlock(pos, block) for player := range l.players { player.OnSetBlock(l, pos, block) } }) } func (l *level) OnAddPlayer(from *player, name string, pos entityPos) { l.Act(from, func() { l.load() if l.loadingState == levelLoaded { from.OnLevelData(l, l.levelInfo, l.compressLevelData()) } var newId levelPlayerId for newId = 0; newId <= 127; newId++ { if l.ids[newId] == nil { break } } if newId == -1 { from.OnLevelError( l, "there are too many players in this area", l.levelInfo, ) } for player, id := range l.players { if player != from { player.OnPlayer(l, newId, name, pos) player.GetInfo(l, func(name string, state playerState) { from.OnPlayer(l, id, name, state.Pos) }, ) } } l.ids[newId] = from l.players[from] = newId }) } func (l *level) OnRemovePlayer(from *player) { l.Act(from, func() { delId := l.players[from] delete(l.ids, delId) delete(l.players, from) for player := range l.players { player.OnRemovePlayer(l, delId) } }) } func (l *level) OnMovePlayer( from *player, pos entityPos, facing entityFacing) { l.Act(from, func() { for player := range l.players { if player != from { player.OnMovePlayer(l, l.players[from], pos, facing) } } }) }