package server import ( "io" "os" "fmt" "regexp" "strings" "git.citrons.xyz/metronode/phony" "git.citrons.xyz/metronode/classic" ) type player struct { phony.Inbox state playerState client *client server *Server name string extensions map[string]bool level *level levelLoaded bool currentMessage string } type authLevel int type playerState struct { LevelId levelId Pos entityPos Facing entityFacing Auth authLevel } const ( defaultAuth = 0 cheatAuth = 100 moderateAuth = 150 opAuth = 200 ConsoleAuth = 900 ) var playerNameRegex = regexp.MustCompile("^[.-_a-zA-Z0-9]*$") func loadPlayerData( from phony.Actor, name string, loaded func(playerState, bool)) { loadDataFile(from, "player/" + name, loaded) } func savePlayerData( from phony.Actor, name string, state playerState, done func()) { saveDataFile(from, "player/" + name, state) if done != nil { dataManager.Act(nil, done) } } func newPlayer( s *Server, cl *client, name string, ext map[string]bool) *player { pl := &player { client: cl, server: s, name: name, extensions: ext, } loadPlayerData(pl, 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) savePlayerData(p, p.name, p.state, 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) sendAuthInfo() { var userType, hacks byte if p.state.Auth >= cheatAuth { userType = classic.OpUser hacks = 1 } p.client.SendPacket(p, &classic.UpdateUserType { Type: userType, }) if p.extensions["HackControl"] { p.client.SendPacket(p, &classic.HackControl { Flying: hacks, NoClip: hacks, Speeding: hacks, SpawnControl: hacks, ThirdPersonView: 1, JumpHeight: 40, }) } } func (p *player) handlePacket(packet classic.Packet) { if p.level == nil || !p.levelLoaded { return } switch pck := packet.(type) { case *classic.SetPosFacingExt: 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: incomplete := pck.PlayerId == 1 && p.extensions["LongerMessages"] message := p.currentMessage + classic.UnpadString(pck.Message) if !incomplete { p.handleChat(message) p.currentMessage = "" } else { p.currentMessage += string(pck.Message[:]) // preserve whitespace } } } func (p *player) handleChat(message string) { isCmd, text := isCommand(message) if strings.TrimSpace(text) == "" { return } if !isCmd { p.server.OnPlayerMessage(p, p.name, text) } else { p.server.ExecuteCommand(p, p.state.Auth, text) } } 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) SetAuthLevel(from phony.Actor, auth authLevel) { p.Act(from, func() { p.state.Auth = auth p.sendAuthInfo() }) } 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) { p.state.LevelId = id p.state.Pos = pos p.level = lvl p.levelLoaded = false lvl.OnAddPlayer(p, p.name, 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.state.Pos = pos p.state.Facing = facing p.client.SendPacket(p, &classic.SpawnPlayerExt { PlayerId: -1, X: classic.Fixed(pos.X), Y: classic.Fixed(pos.Y), Z: classic.Fixed(pos.Z), Yaw: facing.Yaw, Pitch: facing.Pitch, }) }) } 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) OnCommandOutput(from *Server, output string) { p.SendMessage(from, "&e" + output) } func (p *player) OnCommandError(from *Server, err string) { p.SendMessage(from, "&c" + err) } 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.SpawnPlayerExt { PlayerId: -1, Username: classic.PadString(p.name), X: classic.Fixed(p.state.Pos.X), Y: classic.Fixed(p.state.Pos.Y), Z: classic.Fixed(p.state.Pos.Z), Yaw: p.state.Facing.Yaw, Pitch: p.state.Facing.Pitch, }) p.sendAuthInfo() p.levelLoaded = true }) } func (p *player) OnLevelError(from *level, message string, info levelInfo) { p.SendMessage(from, "&cCannot join level: " + message) 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.SpawnPlayerExt { PlayerId: int8(id), Username: classic.PadString(name), X: classic.Fixed(pos.X), Y: classic.Fixed(pos.Y), Z: classic.Fixed(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.SetPosFacingExt { PlayerId: int8(id), X: classic.Fixed(pos.X), Y: classic.Fixed(pos.Y), Z: classic.Fixed(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), }) }