From c3d63652a4b80add587ee17f5c9f3773417203ad Mon Sep 17 00:00:00 2001 From: raven Date: Fri, 20 Mar 2026 14:29:52 -0500 Subject: initial commit --- server/player.go | 242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 server/player.go (limited to 'server/player.go') diff --git a/server/player.go b/server/player.go new file mode 100644 index 0000000..bc6ee38 --- /dev/null +++ b/server/player.go @@ -0,0 +1,242 @@ +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.Reader) { + p.Act(from, func() { + 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), + }) +} -- cgit v1.2.3