basic SAY verb handling
parent
3db69fd8fb
commit
f021ba0133
|
@ -178,6 +178,8 @@ func _main() error {
|
||||||
commandInput := tview.NewInputField().SetLabel("> ")
|
commandInput := tview.NewInputField().SetLabel("> ")
|
||||||
handleInput := func(_ tcell.Key) {
|
handleInput := func(_ tcell.Key) {
|
||||||
input := commandInput.GetText()
|
input := commandInput.GetText()
|
||||||
|
// TODO command history
|
||||||
|
commandInput.SetText("")
|
||||||
// TODO do i need to clear the input's text?
|
// TODO do i need to clear the input's text?
|
||||||
go cs.HandleInput(input)
|
go cs.HandleInput(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -73,6 +74,10 @@ func newServer() (*gameWorldServer, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = db.ClearSessions(); err != nil {
|
||||||
|
return nil, fmt.Errorf("could not clear sessions: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
s := &gameWorldServer{
|
s := &gameWorldServer{
|
||||||
msgRouter: make(map[string]func(*proto.ClientMessage) error),
|
msgRouter: make(map[string]func(*proto.ClientMessage) error),
|
||||||
db: db,
|
db: db,
|
||||||
|
@ -105,6 +110,31 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
|
||||||
}
|
}
|
||||||
send := s.msgRouter[sid]
|
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{
|
msg := &proto.ClientMessage{
|
||||||
Type: proto.ClientMessage_OVERHEARD,
|
Type: proto.ClientMessage_OVERHEARD,
|
||||||
Text: fmt.Sprintf("%s sent command %s with args %s",
|
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 {
|
if err != nil {
|
||||||
log.Printf("failed to send %v to %s: %s", msg, sid, err)
|
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
|
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}
|
si = &proto.SessionInfo{SessionID: sessionID}
|
||||||
|
|
||||||
|
// TODO actually put them in world
|
||||||
|
|
||||||
return
|
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
|
// TODO other server functions
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -24,11 +24,14 @@ type DB interface {
|
||||||
GetAccount(string) (*Account, error)
|
GetAccount(string) (*Account, error)
|
||||||
StartSession(Account) (string, error)
|
StartSession(Account) (string, error)
|
||||||
EndSession(string) error
|
EndSession(string) error
|
||||||
|
ClearSessions() error
|
||||||
|
|
||||||
// Presence
|
// Presence
|
||||||
|
ActiveSessions() ([]Session, error)
|
||||||
AvatarBySessionID(string) (*Object, error)
|
AvatarBySessionID(string) (*Object, error)
|
||||||
BedroomBySessionID(string) (*Object, error)
|
BedroomBySessionID(string) (*Object, error)
|
||||||
MoveInto(toMove Object, container Object) error
|
MoveInto(toMove Object, container Object) error
|
||||||
|
Earshot(Object) ([]Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
|
@ -37,11 +40,17 @@ type Account struct {
|
||||||
Pwhash string
|
Pwhash string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
ID string
|
||||||
|
AccountID int
|
||||||
|
}
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
ID int
|
ID int
|
||||||
Avatar bool
|
Avatar bool
|
||||||
Bedroom bool
|
Bedroom bool
|
||||||
Data map[string]string
|
Data map[string]string
|
||||||
|
OwnerID int
|
||||||
}
|
}
|
||||||
|
|
||||||
type pgDB struct {
|
type pgDB struct {
|
||||||
|
@ -194,11 +203,11 @@ func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
|
||||||
|
|
||||||
// TODO subquery
|
// TODO subquery
|
||||||
stmt := `
|
stmt := `
|
||||||
SELECT id, avatar, data
|
SELECT id, avatar, data, owner
|
||||||
FROM objects WHERE avatar = true AND 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)`
|
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(
|
err = db.pool.QueryRow(context.Background(), stmt, sid).Scan(
|
||||||
&avatar.ID, &avatar.Avatar, &avatar.Data)
|
&avatar.ID, &avatar.Avatar, &avatar.Data, &avatar.OwnerID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +247,54 @@ func (db *pgDB) MoveInto(toMove Object, container Object) error {
|
||||||
return tx.Commit(ctx)
|
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 {
|
func randSmell() string {
|
||||||
// TODO seeding
|
// TODO seeding
|
||||||
smells := []string{
|
smells := []string{
|
||||||
|
|
Loading…
Reference in New Issue