package server import ( "io" "os" "fmt" "math" "bufio" "git.citrons.xyz/metronode/phony" ) type levelId int32 type levelPlayerId int8 type levelInfo struct { Id levelId Size blockPos IsSpawn bool } type level struct { phony.Inbox levelInfo loadingState int server *Server mapM *mapManager // is accessed without synchronization ids map[levelPlayerId]*player players map[*player]levelPlayerId } const ( levelUnloaded = iota levelLoading levelLoaded ) func initLevel(s *Server, info levelInfo) *level { return &level { levelInfo: info, server: s, loadingState: levelUnloaded, mapM: newMapManager(info.Size), ids: make(map[levelPlayerId]*player), players: make(map[*player]levelPlayerId), } } func createNewLevel(s *Server, info levelInfo) *level { l := initLevel(s, info) l.loadingState = levelLoading return l } func (l *level) load() { if l.loadingState == levelUnloaded { l.loadingState = levelLoading var ( id = l.Id v = l.mapM.Blocks ) 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 ) readDataField(rd, &info) v.init(info.Size) err = v.syncDecompressFromStorage(rd) if l.handleLoadError(err) != nil { return } l.Act(&dataManager, func() { l.levelInfo = info l.Id = id l.loadDone() }) }) } } func (l *level) loadDone() { if l.loadingState == levelLoaded { return } l.loadingState = levelLoaded for player := range l.players { player.OnLevelData( l, l.levelInfo, l.mapM.Blocks.syncCompressForNetwork(), ) } } 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 } data := l.mapM.Blocks.syncCompressForStorage() go func() { defer f.Close() defer data.Close() wr := bufio.NewWriter(f) writeDataField(wr, l.levelInfo) io.Copy(wr, data) if wr.Flush() != nil { dataManager.errHand.OnSaveError(l, err) return } }() } func (l *level) generateFlat() { v := l.mapM.Blocks v.syncSetAll(func(yield func(blockType) bool) { var p blockPos for p.Y = 0; p.Y < v.size.Y / 2; p.Y++ { for p.X = 0; p.X < v.size.X; p.X++ { for p.Z = 0; p.Z < v.size.Z; p.Z++ { var block blockType if p.Y == 0 { block = 7 } else if p.Y == v.size.Y/2 - 1 { block = 2 } else if p.Y > v.size.Y/2 - 15 { block = 3 } else if p.Y < v.size.Y/2 { block = 1 } if !yield(block) { return } } } } }) l.Act(nil, l.loadDone) } func (l *level) generateEmpty() { l.Act(nil, l.loadDone) } func (l *level) generateSphere() { v := l.mapM.Blocks v.syncSetAll(func(yield func(blockType) bool) { var (p blockPos; radius = float64(v.size.X) / 2) for p.Y = 0; p.Y < v.size.Y; p.Y++ { for p.X = 0; p.X < v.size.X; p.X++ { for p.Z = 0; p.Z < v.size.Z; p.Z++ { var block blockType dist := math.Sqrt( float64(v.size.X/2 - p.X)*float64(v.size.X/2 - p.X) + float64(v.size.Y/2 - p.Y)*float64(v.size.Y/2 - p.Y) + float64(v.size.Z/2 - p.Z)*float64(v.size.Z/2 - p.Z), ) if dist > radius - 2 && dist <= radius { block = 25 } if !yield(block) { return } } } } }) l.Act(nil, l.loadDone) } func (l *level) generateDebug() { v := l.mapM.Blocks v.syncSetAll(func(yield func(blockType) bool) { for i := 0; i < 256; i++ { if !yield(blockType(i)) { return } } }) l.Act(nil, l.loadDone) } 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.mapM.SetBlock(from, pos, block) l.Act(from, func() { 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.mapM.Blocks.syncCompressForNetwork(), ) } 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) } } }) }