package server import ( "io" "os" "fmt" "regexp" "git.citrons.xyz/metronode/phony" "git.citrons.xyz/metronode/classic" ) type player struct { phony.Inbox state playerState client *client server *Server name string level *level } type playerState struct { LevelId levelId Pos entityPos Facing entityFacing } var playerNameRegex = regexp.MustCompile("^[.-_a-zA-Z0-9]*$") func newPlayer(s *Server, cl *client, name string) *player { pl := &player {client: cl, server: s, name: name} loadDataFile(pl, "player/" + name, func(state playerState, ok bool) { if ok { pl.state = state pl.ChangeLevel(pl, state.LevelId, state.Pos) } else { s.SendToSpawn(pl, pl) } }) return pl } func (p *player) save(done func()) { os.Mkdir("world/player", 0777) saveDataFile(p, "player/" + p.name, p.state) if done != nil { dataManager.Act(nil, done) } } func (p *player) kick(reason string) { p.save(nil) p.client.Disconnect(p, reason) if p.level != nil { p.level.OnRemovePlayer(p) p.level = nil } } func (p *player) handlePacket(packet classic.Packet) { if p.level == nil { return } switch pck := packet.(type) { case *classic.SetPosFacing: p.state.Pos = entityPos { entityCoord(pck.X), entityCoord(pck.Y), entityCoord(pck.Z), } p.state.Facing = entityFacing {pck.Yaw, pck.Pitch} p.level.OnMovePlayer(p, p.state.Pos, p.state.Facing) case *classic.ClientSetBlock: block := blockType(pck.Block) if pck.Mode == classic.BlockDestroyed { block = 0 } pos := blockPos { blockCoord(pck.X), blockCoord(pck.Y), blockCoord(pck.Z), } p.level.SetBlock(p, pos, block) case *classic.Message: p.server.OnPlayerMessage(p, p.name, classic.UnpadString(pck.Message)) } } func (p *player) Save(from phony.Actor, done func()) { p.Act(from, func() { p.save(func() { if done != nil { from.Act(nil, done) } }) }) } func (p *player) Kick(from phony.Actor, reason string) { p.Act(from, func() { p.kick(reason) }) } func (p *player) OnPacket(from phony.Actor, packet classic.Packet) { p.Act(from, func() { p.handlePacket(packet) }) } func (p *player) joinLevel(id levelId, lvl *level, pos entityPos) { lvl.OnAddPlayer(p, p.name, pos) p.level = lvl p.state.LevelId = id p.state.Pos = pos } func (p *player) ChangeLevel(from phony.Actor, lvl levelId, pos entityPos) { p.Act(from, func() { if p.level != nil { p.level.OnRemovePlayer(p) } p.server.GetLevel(p, lvl, func(l *level) { p.joinLevel(lvl, l, pos) }) }) } func (p *player) MovePlayer( from phony.Actor, pos entityPos, facing entityFacing) { p.Act(from, func() { p.level.OnMovePlayer(p, pos, facing) }) } func (p *player) GetInfo(from phony.Actor, reply func(name string, state playerState)) { p.Act(from, func() { name := p.name state := p.state from.Act(nil, func() {reply(name, state)}) }) } func (p *player) SendMessage(from phony.Actor, message string) { p.Act(from, func() { p.client.SendPackets(p, processChatMessage(message)) }) } func (p *player) OnPlayerMessage(from *Server, name string, message string) { p.SendMessage(from, fmt.Sprintf("&7<&b%s&7>&f %s", name, message)) } func (p *player) OnLevelData(from *level, info levelInfo, data io.ReadCloser) { p.Act(from, func() { defer data.Close() if from != p.level { return } var packets []classic.Packet for { var packet classic.LevelDataChunk n, err := io.ReadFull(data, packet.Data[:]) if err == io.EOF || err == io.ErrUnexpectedEOF { if n == 0 { break } } else if err != nil { panic(err) } packet.Length = int16(n) packets = append(packets, &packet) } for i := 0; i < len(packets); i++ { chunk := packets[i].(*classic.LevelDataChunk) chunk.PercentComplete = byte(i * 100 / len(packets)) } p.client.SendPackets(p, packets) p.client.SendPacket(p, &classic.LevelFinalize { Width: int16(info.Size.X), Height: int16(info.Size.Y), Length: int16(info.Size.Z), }) p.client.SendPacket(p, &classic.SpawnPlayer { PlayerId: -1, Username: classic.PadString(p.name), X: classic.FShort(p.state.Pos.X), Y: classic.FShort(p.state.Pos.Y), Z: classic.FShort(p.state.Pos.Z), }) }) } func (p *player) OnLevelError(from *level, message string, info levelInfo) { p.SendMessage(from, "&cCannot join level: " + message) fmt.Println(info) if !info.IsSpawn { p.Act(from, func() { p.server.SendToSpawn(p, p) }) } else { p.Kick(from, "Error: " + message) } } func (p *player) OnPlayer( from *level, id levelPlayerId, name string, pos entityPos) { p.Act(from, func() { p.client.SendPacket(p, &classic.SpawnPlayer { PlayerId: int8(id), Username: classic.PadString(name), X: classic.FShort(pos.X), Y: classic.FShort(pos.Y), Z: classic.FShort(pos.Z), }) }) } func (p *player) OnRemovePlayer(from *level, id levelPlayerId) { p.Act(from, func() { p.client.SendPacket(p, &classic.DespawnPlayer {int8(id)}) }) } func (p *player) OnMovePlayer( from *level, id levelPlayerId, pos entityPos, facing entityFacing) { p.Act(from, func() { p.client.SendPacket(p, &classic.SetPosFacing { PlayerId: int8(id), X: classic.FShort(pos.X), Y: classic.FShort(pos.Y), Z: classic.FShort(pos.Z), Yaw: facing.Yaw, Pitch: facing.Pitch, }) }) } func (p *player) OnSetBlock(from *level, pos blockPos, block blockType) { p.client.SendPacket(p, &classic.SetBlock { X: int16(pos.X), Y: int16(pos.Y), Z: int16(pos.Z), Block: byte(block), }) }