summaryrefslogtreecommitdiff
path: root/server/player.go
diff options
context:
space:
mode:
Diffstat (limited to 'server/player.go')
-rw-r--r--server/player.go242
1 files changed, 242 insertions, 0 deletions
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),
+ })
+}