basic SAY verb handling

trunk
vilmibm 2022-12-13 23:52:29 -08:00
parent 3db69fd8fb
commit f021ba0133
3 changed files with 184 additions and 15 deletions

View File

@ -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)
}

View File

@ -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,6 +110,31 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
}
send := s.msgRouter[sid]
// TODO what is the implication of returning an error from this function?
avatar, err := s.db.AvatarBySessionID(sid)
if err != nil {
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)
}
}
/*
msg := &proto.ClientMessage{
Type: proto.ClientMessage_OVERHEARD,
Text: fmt.Sprintf("%s sent command %s with args %s",
@ -118,10 +148,7 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
if err != nil {
log.Printf("failed to send %v to %s: %s", msg, sid, 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
*/
}
}
@ -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() {

View File

@ -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{