package server import ( "io" "os" "fmt" "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) 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) TrySetBlock( from phony.Actor, pos blockPos, block blockType, auth authLevel, reject func(realBlock blockType)) { l.mapM.GetBlock(from, pos, func(curBlock blockType) { if blockDefinitions[curBlock].AuthLevel > auth { from.Act(nil, func() {reject(curBlock)}) return } def, ok := blockDefinitions[block] if !ok && block > blockStoneBricks { from.Act(nil, func() {reject(curBlock)}) return } if def.AuthLevel > auth { from.Act(nil, func() {reject(curBlock)}) return } l.SetBlock(nil, 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) } } }) }