summaryrefslogtreecommitdiff
path: root/server/level.go
diff options
context:
space:
mode:
Diffstat (limited to 'server/level.go')
-rw-r--r--server/level.go266
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)
+ }
+ }
+ })
+}