package net import ( "bufio" "fmt" "log" "net" "strings" ) type SessionState int const ( StateAccountName SessionState = iota StatePassword StateNewAccountPass StateNewAccountConfirm StateMenu StateNewCharName StateNewCharConfirm StateRenameAccount StateRenameChar StateRenameCharName StateDeleteChar StatePurgeAccount StateGame StateChangeDescription StateTalk StateDropAllConfirm ) type Session struct { Conn net.Conn Reader *bufio.Reader State SessionState Account *AccountEntry Player interface{} // *player.Player once character is selected PendingChar string // char being renamed/deleted PendingPass string // first password during signup Disconnecting bool DisconnectTicks int } type AccountEntry struct { Name string PasswordHash string Characters []string Aliases map[string]string } type Server struct { listener net.Listener hub *Hub } type Hub struct { sessions map[*Session]bool rooms map[int]map[*Session]bool onRemove func(*Session) } func NewHub() *Hub { return &Hub{ sessions: make(map[*Session]bool), rooms: make(map[int]map[*Session]bool), } } func (h *Hub) OnRemove(cb func(*Session)) { h.onRemove = cb } func (h *Hub) Add(s *Session) { h.sessions[s] = true } func (h *Hub) Remove(s *Session) { if s.Player != nil && s.State == StateGame && !s.Disconnecting { s.Disconnecting = true s.DisconnectTicks = 10 return } h.HardRemove(s) } func (h *Hub) HardRemove(s *Session) { delete(h.sessions, s) for _, room := range h.rooms { delete(room, s) } if h.onRemove != nil { h.onRemove(s) } } func (h *Hub) EnterRoom(s *Session, roomID int) { h.LeaveRoom(s) if h.rooms[roomID] == nil { h.rooms[roomID] = make(map[*Session]bool) } h.rooms[roomID][s] = true } func (h *Hub) LeaveRoom(s *Session) { for _, room := range h.rooms { delete(room, s) } } func (h *Hub) AllSessions() []*Session { var out []*Session for s := range h.sessions { out = append(out, s) } return out } func (h *Hub) PlayersInRoom(roomID int) []*Session { var out []*Session if room, ok := h.rooms[roomID]; ok { for s := range room { out = append(out, s) } } return out } func NewServer(addr string) (*Server, error) { l, err := net.Listen("tcp", addr) if err != nil { return nil, err } return &Server{listener: l, hub: NewHub()}, nil } func (s *Server) Hub() *Hub { return s.hub } func (s *Server) ListenAndServe(handler func(*Session, string)) error { for { conn, err := s.listener.Accept() if err != nil { return err } session := &Session{ Conn: conn, Reader: bufio.NewReader(conn), State: StateAccountName, } s.hub.Add(session) go s.handleSession(session, handler) } } func (s *Server) handleSession(session *Session, handler func(*Session, string)) { defer func() { if r := recover(); r != nil { log.Printf("session panic: %v", r) } s.hub.Remove(session) session.Conn.Close() }() _, _ = session.Conn.Write([]byte("\033[2J\033[H")) // clear screen welcomeart := ` , 3333333 333 333 333 3333333 3333333 | 33! 33! 333 33! 33! 333 33! 333 ':. + -+- * 3!! 3!3!3!3! !!3 3!3!!3! 3!3 !3! '::._ * | !!: !!: !!! !!: !!: :!! !!: !!! '._) : : : : : : : : :: : : , . * , , welcome to third collapse . * 3333333 333333 333 , 333 333333 3333333 333333 33333333 !33 33! 333 33! 33! 33! 333 33! 333 !33 33! !3! 3!3 !3! 3!! 3!! + 3!3!3!3! 3!33!3! !33!! 3!!!:! :!! !!: !!! !!: !!: !!: !!! !!: !:! !!: :: :: : : :. : : ::.: : : ::.: : : : : : ::.: : : :: ::: ` session.Write(welcomeart) session.Write("ACCOUNT NAME> ") for { line, err := session.Reader.ReadString('\n') if err != nil { log.Printf("session read error: %v", err) return } line = strings.TrimSpace(line) handler(session, line) } } func (sess *Session) Write(msg string) { _, _ = sess.Conn.Write([]byte(msg)) } func (sess *Session) WriteLine(msg string) { _, _ = sess.Conn.Write([]byte(msg + "\r\n")) } func (sess *Session) WriteLines(lines ...string) { for _, l := range lines { sess.WriteLine(l) } } func (sess *Session) Writef(format string, args ...interface{}) { sess.Write(fmt.Sprintf(format, args...)) }