diff options
Diffstat (limited to 'server/level.go')
| -rw-r--r-- | server/level.go | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/server/level.go b/server/level.go new file mode 100644 index 0000000..577d0dc --- /dev/null +++ b/server/level.go @@ -0,0 +1,266 @@ +package server + +import ( + "io" + "os" + "fmt" + "bytes" + "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.OnLevelError(&dataManager, err) != nil { + return + } + defer f.Close() + var ( + info levelInfo + blocks []byte + ) + err = binary.Read(f, binary.BigEndian, &info) + if l.OnLevelError(&dataManager, err) != nil { + return + } + z, err := gzip.NewReader(f) + if l.OnLevelError(&dataManager, err) != nil { + return + } + z.Read(make([]byte, 4)) + blocks, err = io.ReadAll(z) + if l.OnLevelError(&dataManager, 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) OnLevelError(from phony.Actor, err error) error { + if err == nil { + return err + } + l.Act(from, func() { + l.loadingState = levelUnloaded + dataManager.errHand.OnLoadError(l, err) + errString := err.Error() + if os.IsNotExist(err) { + errString = "level not found" + } + for player := range l.players { + player.OnLevelError(l, errString, l.levelInfo) + } + }) + return err +} + +func (l *level) save(done func()) { + 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() + + err = binary.Write(f, binary.BigEndian, l.levelInfo) + if err != nil { + dataManager.errHand.OnSaveError(l, err) + return + } + data := l.compressLevelData() + _, err = io.Copy(f, data) + if err != nil { + dataManager.errHand.OnSaveError(l, err) + return + } + if done != nil { + dataManager.Act(nil, done) + } +} + +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.Reader { + 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) + } + } + }) +} |
