basic SAY verb handling
This commit is contained in:
		
							parent
							
								
									3db69fd8fb
								
							
						
					
					
						commit
						f021ba0133
					
				@ -178,6 +178,8 @@ func _main() error {
 | 
			
		||||
	commandInput := tview.NewInputField().SetLabel("> ")
 | 
			
		||||
	handleInput := func(_ tcell.Key) {
 | 
			
		||||
		input := commandInput.GetText()
 | 
			
		||||
		// TODO command history
 | 
			
		||||
		commandInput.SetText("")
 | 
			
		||||
		// TODO do i need to clear the input's text?
 | 
			
		||||
		go cs.HandleInput(input)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
@ -73,6 +74,10 @@ func newServer() (*gameWorldServer, error) {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = db.ClearSessions(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("could not clear sessions: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s := &gameWorldServer{
 | 
			
		||||
		msgRouter: make(map[string]func(*proto.ClientMessage) error),
 | 
			
		||||
		db:        db,
 | 
			
		||||
@ -105,23 +110,45 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
 | 
			
		||||
		}
 | 
			
		||||
		send := s.msgRouter[sid]
 | 
			
		||||
 | 
			
		||||
		msg := &proto.ClientMessage{
 | 
			
		||||
			Type: proto.ClientMessage_OVERHEARD,
 | 
			
		||||
			Text: fmt.Sprintf("%s sent command %s with args %s",
 | 
			
		||||
				sid, cmd.Verb, cmd.Rest),
 | 
			
		||||
		}
 | 
			
		||||
		// TODO what is the implication of returning an error from this function?
 | 
			
		||||
 | 
			
		||||
		speaker := "ECHO"
 | 
			
		||||
		msg.Speaker = &speaker
 | 
			
		||||
 | 
			
		||||
		err = send(msg)
 | 
			
		||||
		avatar, err := s.db.AvatarBySessionID(sid)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("failed to send %v to %s: %s", msg, sid, err)
 | 
			
		||||
			return s.HandleError(send, err)
 | 
			
		||||
		}
 | 
			
		||||
		log.Printf("found avatar %#v", avatar)
 | 
			
		||||
 | 
			
		||||
		switch cmd.Verb {
 | 
			
		||||
		case "say":
 | 
			
		||||
			if err = s.HandleSay(avatar, cmd.Rest); err != nil {
 | 
			
		||||
				s.HandleError(func(_ *proto.ClientMessage) error { return nil }, err)
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			msg := &proto.ClientMessage{
 | 
			
		||||
				Type: proto.ClientMessage_WHISPER,
 | 
			
		||||
				Text: fmt.Sprintf("unknown verb: %s", cmd.Verb),
 | 
			
		||||
			}
 | 
			
		||||
			if err = send(msg); err != nil {
 | 
			
		||||
				s.HandleError(send, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO find the user who ran action via SessionInfo
 | 
			
		||||
		// TODO get area of effect, which should include the sender
 | 
			
		||||
		// TODO dispatch the command to each affected object
 | 
			
		||||
		/*
 | 
			
		||||
 | 
			
		||||
			msg := &proto.ClientMessage{
 | 
			
		||||
				Type: proto.ClientMessage_OVERHEARD,
 | 
			
		||||
				Text: fmt.Sprintf("%s sent command %s with args %s",
 | 
			
		||||
					sid, cmd.Verb, cmd.Rest),
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			speaker := "ECHO"
 | 
			
		||||
			msg.Speaker = &speaker
 | 
			
		||||
 | 
			
		||||
			err = send(msg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("failed to send %v to %s: %s", msg, sid, err)
 | 
			
		||||
			}
 | 
			
		||||
		*/
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -194,11 +221,94 @@ func (s *gameWorldServer) Login(ctx context.Context, auth *proto.AuthInfo) (si *
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	av, err := s.db.AvatarBySessionID(sessionID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to find avatar for %s: %w", sessionID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bedroom, err := s.db.BedroomBySessionID(sessionID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to find bedroom for %s: %w", sessionID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = s.db.MoveInto(*av, *bedroom)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to move %d into %d: %w", av.ID, bedroom.ID, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	si = &proto.SessionInfo{SessionID: sessionID}
 | 
			
		||||
 | 
			
		||||
	// TODO actually put them in world
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *gameWorldServer) HandleSay(avatar *db.Object, msg string) error {
 | 
			
		||||
	name := avatar.Data["name"]
 | 
			
		||||
	if name == "" {
 | 
			
		||||
		// TODO determine this based on a hash or something
 | 
			
		||||
		name = "a mysterious figure"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	heard, err := s.db.Earshot(*avatar)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Printf("found %#v in earshot of %#v\n", heard, avatar)
 | 
			
		||||
 | 
			
		||||
	as, err := s.db.ActiveSessions()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendErrs := []error{}
 | 
			
		||||
 | 
			
		||||
	for _, h := range heard {
 | 
			
		||||
		// TODO once we have a script engine, deliver the HEARS event
 | 
			
		||||
		for _, sess := range as {
 | 
			
		||||
			if sess.AccountID == h.OwnerID {
 | 
			
		||||
				cm := proto.ClientMessage{
 | 
			
		||||
					Type:    proto.ClientMessage_OVERHEARD,
 | 
			
		||||
					Text:    msg,
 | 
			
		||||
					Speaker: &name,
 | 
			
		||||
				}
 | 
			
		||||
				err = s.msgRouter[sess.ID](&cm)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					sendErrs = append(sendErrs, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(sendErrs) > 0 {
 | 
			
		||||
		errMsg := "send errors: "
 | 
			
		||||
		for i, err := range sendErrs {
 | 
			
		||||
			errMsg += err.Error()
 | 
			
		||||
			if i < len(sendErrs)-1 {
 | 
			
		||||
				errMsg += ", "
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return errors.New(errMsg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *gameWorldServer) HandleError(send func(*proto.ClientMessage) error, err error) error {
 | 
			
		||||
	log.Printf("error: %s", err.Error())
 | 
			
		||||
	msg := &proto.ClientMessage{
 | 
			
		||||
		Type: proto.ClientMessage_WHISPER,
 | 
			
		||||
		Text: "server error :(",
 | 
			
		||||
	}
 | 
			
		||||
	err = send(msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("error sending to client: %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO other server functions
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
 | 
			
		||||
@ -24,11 +24,14 @@ type DB interface {
 | 
			
		||||
	GetAccount(string) (*Account, error)
 | 
			
		||||
	StartSession(Account) (string, error)
 | 
			
		||||
	EndSession(string) error
 | 
			
		||||
	ClearSessions() error
 | 
			
		||||
 | 
			
		||||
	// Presence
 | 
			
		||||
	ActiveSessions() ([]Session, error)
 | 
			
		||||
	AvatarBySessionID(string) (*Object, error)
 | 
			
		||||
	BedroomBySessionID(string) (*Object, error)
 | 
			
		||||
	MoveInto(toMove Object, container Object) error
 | 
			
		||||
	Earshot(Object) ([]Object, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Account struct {
 | 
			
		||||
@ -37,11 +40,17 @@ type Account struct {
 | 
			
		||||
	Pwhash string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Session struct {
 | 
			
		||||
	ID        string
 | 
			
		||||
	AccountID int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Object struct {
 | 
			
		||||
	ID      int
 | 
			
		||||
	Avatar  bool
 | 
			
		||||
	Bedroom bool
 | 
			
		||||
	Data    map[string]string
 | 
			
		||||
	OwnerID int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type pgDB struct {
 | 
			
		||||
@ -194,11 +203,11 @@ func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
 | 
			
		||||
 | 
			
		||||
	// TODO subquery
 | 
			
		||||
	stmt := `
 | 
			
		||||
	SELECT id, avatar, data
 | 
			
		||||
	SELECT id, avatar, data, owner
 | 
			
		||||
	FROM objects WHERE avatar = true AND owner = (
 | 
			
		||||
		SELECT a.id FROM sessions s JOIN accounts a ON s.account = a.id WHERE s.id = $1)`
 | 
			
		||||
	err = db.pool.QueryRow(context.Background(), stmt, sid).Scan(
 | 
			
		||||
		&avatar.ID, &avatar.Avatar, &avatar.Data)
 | 
			
		||||
		&avatar.ID, &avatar.Avatar, &avatar.Data, &avatar.OwnerID)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -238,6 +247,54 @@ func (db *pgDB) MoveInto(toMove Object, container Object) error {
 | 
			
		||||
	return tx.Commit(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *pgDB) Earshot(obj Object) ([]Object, error) {
 | 
			
		||||
	stmt := `
 | 
			
		||||
	SELECT id, avatar, bedroom, data, owner FROM objects
 | 
			
		||||
	WHERE id IN (
 | 
			
		||||
		SELECT contained FROM contains
 | 
			
		||||
		WHERE container = (
 | 
			
		||||
			SELECT container FROM contains WHERE contained = $1 LIMIT 1))`
 | 
			
		||||
	rows, err := db.pool.Query(context.Background(), stmt, obj.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out := []Object{}
 | 
			
		||||
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		heard := Object{}
 | 
			
		||||
		if err = rows.Scan(&heard.ID, &heard.Avatar, &heard.Bedroom, &heard.Data, &heard.OwnerID); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, heard)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return out, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *pgDB) ActiveSessions() (out []Session, err error) {
 | 
			
		||||
	stmt := `SELECT id, account FROM sessions`
 | 
			
		||||
	rows, err := db.pool.Query(context.Background(), stmt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for rows.Next() {
 | 
			
		||||
		s := Session{}
 | 
			
		||||
		if err = rows.Scan(&s.ID, &s.AccountID); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		out = append(out, s)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (db *pgDB) ClearSessions() (err error) {
 | 
			
		||||
	_, err = db.pool.Exec(context.Background(), "DELETE FROM sessions")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func randSmell() string {
 | 
			
		||||
	// TODO seeding
 | 
			
		||||
	smells := []string{
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user