package funmow import ( "bufio" "fmt" "log" "net" "strings" "unicode" ) type Client struct { conn net.Conn connectionID int playerID DBRef authenticated bool connected bool reader *bufio.Reader commandChan chan string inbound chan PlayerEvent outbound chan PlayerEvent eventDistributor *EventDistributor db *DB factory *ObjectFactory } func NewClient(conn net.Conn, connectionID int, e *EventDistributor, w *DB) *Client { c := new(Client) c.conn = conn c.connectionID = connectionID c.reader = bufio.NewReader(conn) c.authenticated = false c.connected = true c.commandChan = make(chan string) c.inbound = make(chan PlayerEvent) c.eventDistributor = e c.db = w c.factory = NewObjectFactory(w) return c } func (c *Client) Run() { log.Print("Received connection from ", c.conn.RemoteAddr()) go func() { for c.connected { message, err := c.reader.ReadString('\n') if err != nil { c.connected = false return } message = strings.TrimSpace(message) if len(message) > 0 { c.commandChan <- message } } }() c.splash() for c.connected { select { case m := <-c.inbound: c.handleInboundEvent(m) case cmd := <-c.commandChan: if c.authenticated { c.sendCommand(cmd) } else { c.handleLoginPhase(cmd) } } } c.conn.Close() c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, messageType: EventTypeTeardown, connectionID: c.connectionID} log.Print("Lost connection from ", c.conn.RemoteAddr()) } func (c *Client) splash() { c.write("Welcome to...\n") c.write(" ___ __ __ ___ __ __\n") c.write(" | __| _ _ _ _ | \\/ | / _ \\ \\ \\ / /\n") c.write(" | _| | || | | ' \\ | |\\/| | | (_) | \\ \\/\\/ /\n") c.write(" |_| \\_,_| |_||_| |_| |_| \\___/ \\_/\\_/\n\n") c.write("use connect to login.\n") } func (c *Client) handleLoginPhase(message string) { fields := strings.FieldsFunc(message, func(c rune) bool { return unicode.IsSpace(c) }) switch fields[0] { case "help": c.write("Commands:\n\tcon[nect] \n\tquit\n") case "con": fallthrough case "connect": if len(fields) == 3 { player, found := c.db.GetPlayer(fields[1]) if !found || (found && player.Password != fields[2]) { c.write("Bad username or password.\n") break } c.playerID = player.ID c.authenticated = true // when a player authenticates, the ipc channels get turned on subReq := EventSubscribeRequest{ connectionID: c.connectionID, playerID: c.playerID, inbound: c.inbound, outbound: make(chan chan PlayerEvent), } c.outbound = c.eventDistributor.Subscribe(subReq) c.sendCommand("look") } else { c.write("What?\n") } case "create": if len(fields) == 3 { _, found := c.db.GetPlayer(fields[1]) if found { c.write("That name is already taken.\n") break } playerID, _ := c.db.CreatePlayer(strings.TrimSpace(fields[1]), strings.TrimSpace(fields[2]), nil) _, found = c.db.Fetch(playerID) if !found { c.write("I can't even.\n") break } c.write("You have been created. Now you can connect!\n") } else { c.write("What?\n") } case "quit": c.connected = false default: c.write("What?\n") } } func (c *Client) handleInboundEvent(m PlayerEvent) { switch m.messageType { case EventTypeOutput: c.write("%s\n", m.message) case EventTypeQuit: c.connected = false } } func (c *Client) write(format string, a ...interface{}) { fmt.Fprintf(c.conn, format, a...) } func (c *Client) sendCommand(message string) { c.outbound <- PlayerEvent{src: c.playerID, dst: c.playerID, message: message, messageType: EventTypeCommand, connectionID: c.connectionID} }