summaryrefslogtreecommitdiff
path: root/server/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'server/server.go')
-rw-r--r--server/server.go396
1 files changed, 396 insertions, 0 deletions
diff --git a/server/server.go b/server/server.go
new file mode 100644
index 0000000..13e06f6
--- /dev/null
+++ b/server/server.go
@@ -0,0 +1,396 @@
+package server
+
+import (
+ "os"
+ "net"
+ "log"
+ "fmt"
+ "time"
+ "binary"
+ "os/signal"
+ "git.citrons.xyz/metronode/classic"
+ "git.citrons.xyz/metronode/phony"
+)
+
+var SoftwareName = "Metronode"
+
+type ServerInfo struct {
+ Name string
+ Motd string
+}
+
+type Server struct {
+ phony.Inbox
+ worldState
+ info ServerInfo
+ clients map[*client]bool
+ players map[string]*player
+ levels map[levelId]*level
+ listener net.Listener
+ stopping bool
+ stopped chan struct{}
+}
+
+type worldState struct {
+ SpawnLevel levelId
+ SpawnPos entityPos
+}
+
+func NewServer(info ServerInfo) *Server {
+ s := &Server {
+ info: info,
+ clients: make(map[*client]bool),
+ players: make(map[string]*player),
+ levels: make(map[levelId]*level),
+ stopped: make(chan struct{}),
+ }
+ err := os.Mkdir("world", 0777)
+ if err == nil {
+ spawnLevel := s.newLevel(levelInfo {
+ Id: 0,
+ Size: blockPos {X: 256, Y: 256, Z: 256},
+ IsSpawn: true,
+ })
+ spawnLevel.generateFlat()
+ s.SpawnPos = entityPos {
+ 128*blockSize,
+ 128*blockSize + playerHeight,
+ 128*blockSize,
+ }
+ } else {
+ f, err := os.Open("world/world.bin")
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = binary.Read(f, binary.BigEndian, &s.worldState)
+ if err != nil {
+ log.Fatal("read world data: %s", err)
+ }
+ return s
+}
+
+func (s *Server) Serve(ln net.Listener) {
+ dataManager.errHand = s
+ s.Act(nil, func() {s.listener = ln})
+ go func() {
+ defer s.Stop(nil)
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ s.Act(nil, func() {
+ s.clients[newClient(s, s.info, conn)] = true
+ })
+ }
+ }()
+ var (
+ ping = time.Tick(10 * time.Second)
+ tick = time.Tick(time.Second / 20)
+ save = time.Tick(time.Minute)
+ sigterm = make(chan os.Signal)
+ )
+ signal.Notify(sigterm, os.Interrupt)
+ defer signal.Stop(sigterm)
+ for {
+ select {
+ case <-ping: s.SendPings()
+ case <-tick: s.Tick()
+ case <-save: s.Save(nil)
+ case <-sigterm: s.Stop(nil)
+ case <-s.stopped: return
+ }
+ }
+}
+
+func (s *Server) stop() {
+ if s.stopping {
+ return
+ }
+ log.Println("stopping the server. please wait...")
+ s.listener.Close()
+ s.stopping = true
+ var (
+ savedPlayers int
+ savedLevels int
+ )
+ checkSaved := func() {
+ if savedLevels >= len(s.levels) && savedPlayers >= len(s.players) {
+ close(s.stopped)
+ for client := range s.clients {
+ client.Disconnect(s, "Shutting down...")
+ }
+ }
+ }
+ for _, player := range s.players {
+ player.Save(s, func() {
+ savedPlayers++
+ checkSaved()
+ })
+ }
+ for _, level := range s.levels {
+ level.Save(s, func() {
+ savedLevels++
+ checkSaved()
+ })
+ }
+}
+
+func (s *Server) Stop(from phony.Actor) {
+ s.Act(from, s.stop)
+}
+
+func (s *Server) Save(from phony.Actor) {
+ s.Act(from, func() {
+ for _, player := range s.players {
+ player.Save(s, nil)
+ }
+ for _, level := range s.levels {
+ level.Save(s, nil)
+ }
+ })
+}
+
+func (s *Server) SendPings() {
+ s.Act(nil, func() {
+ for cl := range s.clients {
+ cl.SendPing(s)
+ }
+ })
+}
+
+func (s *Server) OnDisconnect(cl *client, username string, pl *player) {
+ s.Act(cl, func() {
+ delete(s.clients, cl)
+ if s.stopping {
+ return
+ }
+ if s.players[username] == pl {
+ delete(s.players, username)
+ }
+ if username == "" {
+ return
+ }
+ s.Broadcast(nil, fmt.Sprintf("&e%s has left", username))
+ })
+}
+
+func (s *Server) OnPlayerMessage(from *player, name string, message string) {
+ s.Act(from, func() {
+ for _, player := range s.players {
+ player.OnPlayerMessage(s, name, message)
+ }
+ })
+}
+
+func (s *Server) OnLoadError(from phony.Actor, err error) {
+ log.Printf("error loading world: %s", err)
+}
+
+func (s *Server) OnSaveError(from phony.Actor, err error) {
+ log.Printf("error saving world: %s", err)
+ s.Broadcast(from, fmt.Sprintf("&4Error saving world: %s", err))
+}
+
+func (s *Server) Tick() {
+}
+
+func (s *Server) newLevel(info levelInfo) *level {
+ l := newLevel(s, info)
+ s.levels[info.Id] = l
+ return l
+}
+
+func (s *Server) newPlayer(cl *client, name string) *player {
+ pl := newPlayer(s, cl, name)
+ s.players[name] = pl
+ return pl
+}
+
+func (s *Server) NewPlayer(
+ from phony.Actor, cl *client, name string, reply func(*player)) {
+ s.Act(from, func() {
+ s.Broadcast(nil, fmt.Sprintf("&e%s has joined", name))
+ if s.players[name] != nil {
+ s.players[name].Act(s, func() {
+ s.players[name].kick("Replaced by new connection")
+ s.Act(s.players[name], func() {
+ s.newPlayer(cl, name)
+ s.GetPlayer(from, name, reply)
+ })
+ })
+ } else {
+ s.newPlayer(cl, name)
+ s.GetPlayer(from, name, reply)
+ }
+ })
+}
+
+func (s *Server) getLevel(lvl levelId) *level {
+ if s.levels[lvl] == nil {
+ s.levels[lvl] = loadLevel(s, lvl, lvl == s.spawnLevel)
+ }
+ return s.levels[lvl]
+}
+
+func (s *Server) GetLevel(
+ from phony.Actor, lvl levelId, reply func(*level)) {
+ s.Act(from, func() {
+ if s.stopping {
+ return
+ }
+ l := s.getLevel(lvl)
+ from.Act(nil, func() {reply(l)})
+ })
+}
+
+func (s *Server) SendToSpawn(from phony.Actor, pl *player) {
+ s.Act(from, func() {
+ pl.ChangeLevel(s, s.spawnLevel, s.spawnPos)
+ })
+}
+
+func (s *Server) GetPlayer(
+ from phony.Actor, name string, reply func(*player)) {
+ s.Act(from, func() {
+ from.Act(nil, func() {reply(s.players[name])})
+ })
+}
+
+func (s *Server) Broadcast(from phony.Actor, message string) {
+ for _, player := range s.players {
+ player.SendMessage(s, message)
+ }
+}
+
+type client struct {
+ phony.Inbox
+ server *Server
+ conn net.Conn
+ username string
+ player *player
+}
+
+func newClient(server *Server, srvInfo ServerInfo, conn net.Conn) *client {
+ cl := &client {server: server}
+ cl.Act(nil, func() {
+ cl.performHandshake(conn, srvInfo)
+ if cl.conn != nil {
+ go cl.readPackets(cl.conn)
+ }
+ })
+ return cl
+}
+
+func (cl *client) performHandshake(conn net.Conn, srvInfo ServerInfo) {
+ cl.conn = conn
+ conn.SetDeadline(time.Now().Add(10 * time.Second))
+
+ packet, err := classic.SReadPacket(conn)
+ if cl.handleError(err) != nil {
+ return
+ }
+ switch pid := packet.(type) {
+ case *classic.PlayerId:
+ if pid.Version != 7 {
+ cl.disconnect(
+ "Please join on protocol version 7 (Minecraft Classic 0.30 / "+
+ "ClassiCube)",
+ )
+ }
+ cl.username = classic.UnpadString(pid.Username)
+ default:
+ cl.disconnect("Expected handshake")
+ return
+ }
+ err = classic.WritePacket(conn, &classic.ServerId {
+ Version: 7,
+ ServerName: classic.PadString(srvInfo.Name),
+ Motd: classic.PadString(srvInfo.Motd),
+ })
+ if cl.handleError(err) != nil {
+ return
+ }
+ cl.server.NewPlayer(cl, cl, cl.username, func(pl *player) {
+ cl.player = pl
+ })
+
+ conn.SetDeadline(time.Time{})
+}
+
+func (cl *client) readPackets(conn net.Conn) {
+ for {
+ packet, err := classic.SReadPacket(conn)
+ cl.Act(nil, func() {
+ if cl.handleError(err) != nil {
+ return
+ }
+ if cl.player != nil {
+ cl.player.OnPacket(cl, packet)
+ }
+ })
+ if err != nil {
+ return
+ }
+ }
+}
+
+func (cl *client) handleError(err error) error {
+ if err == nil {
+ return err
+ }
+ cl.disconnect(err.Error())
+ return err
+}
+
+func (cl *client) disconnect(reason string) {
+ if cl.player != nil {
+ cl.player.Kick(cl, reason)
+ cl.player = nil
+ }
+ if cl.conn != nil {
+ cl.server.OnDisconnect(cl, cl.username, cl.player)
+ log.Printf("disconnecting client (%s): %s", cl.username, reason)
+ cl.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ classic.WritePacket(cl.conn, &classic.DisconnectPlayer {
+ Reason: classic.PadString(reason),
+ })
+ cl.conn.Close()
+ cl.conn = nil
+ }
+}
+
+func (cl *client) SendPacket(from phony.Actor, packet classic.Packet) {
+ cl.Act(from, func() {
+ if cl.conn != nil {
+ cl.handleError(classic.WritePacket(cl.conn, packet))
+ }
+ })
+}
+
+func (cl *client) SendPackets(from phony.Actor, packets []classic.Packet) {
+ cl.Act(from, func() {
+ for _, packet := range packets {
+ if cl.conn == nil {
+ return
+ }
+ cl.handleError(classic.WritePacket(cl.conn, packet))
+ }
+ })
+}
+
+func (cl *client) SendPing(from phony.Actor) {
+ cl.Act(from, func() {
+ if cl.conn != nil {
+ cl.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+ cl.handleError(classic.WritePacket(cl.conn, &classic.Ping{}))
+ }
+ })
+}
+
+func (cl *client) Disconnect(from phony.Actor, reason string) {
+ cl.Act(from, func() {
+ cl.disconnect(reason)
+ })
+}