From 1664d678cbb55f8cae1478ec22c86b09112efe00 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Thu, 22 Dec 2022 22:03:37 -0800 Subject: [PATCH] messy WIP; default objects + witch stuff --- server/cmd/main.go | 64 +++++++++++++++------- server/db/db.go | 120 ++++++++++++++++++++++++++++++++++++------ server/db/schema.sql | 2 +- server/witch/witch.go | 7 ++- 4 files changed, 156 insertions(+), 37 deletions(-) diff --git a/server/cmd/main.go b/server/cmd/main.go index b6e0afd..4f770e6 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -84,6 +84,10 @@ func newServer() (*gameWorldServer, error) { return nil, err } + if err = db.Ensure(); err != nil { + return nil, fmt.Errorf("failed to ensure default entities: %w", err) + } + if err = db.ClearSessions(); err != nil { return nil, fmt.Errorf("could not clear sessions: %w", err) } @@ -91,12 +95,17 @@ func newServer() (*gameWorldServer, error) { s := &gameWorldServer{ msgRouter: make(map[string]func(*proto.ClientMessage) error), db: db, - Gateway: witch.NewGateway(), } + gw := witch.NewGateway(s.HandleCmd) + s.Gateway = gw return s, nil } +func (s *gameWorldServer) HandleCmd(verb, rest string, sender *db.Object) { + // TODO +} + func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error { var sid string for { @@ -129,20 +138,26 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error } 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) + s.HandleCmd(cmd.Verb, cmd.Rest, 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) + } } - 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) - } - } + + */ /* @@ -202,14 +217,25 @@ func (s *gameWorldServer) Register(ctx context.Context, auth *proto.AuthInfo) (s return nil, fmt.Errorf("failed to find avatar for %s: %w", sessionID, err) } - bedroom, err := s.db.BedroomBySessionID(sessionID) + /* + 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) + } + */ + + foyer, err := s.db.GetObject("system", "foyer") if err != nil { - return nil, fmt.Errorf("failed to find bedroom for %s: %w", sessionID, err) + return nil, fmt.Errorf("failed to find foyer: %w", 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) + if err = s.db.MoveInto(*av, *foyer); err != nil { + return nil, fmt.Errorf("failed to move %d into %d: %w", av.ID, foyer.ID, err) } // TODO send room info, avatar info to client (need to figure this out and update proto) diff --git a/server/db/db.go b/server/db/db.go index eafe2ff..aa2bc19 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -12,7 +12,7 @@ import ( "github.com/jackc/pgx/v4/pgxpool" ) -// go:embed schema.sql +//go:embed schema.sql var schema string // TODO I have a suspicion that I'm going to want to move to an ORM like model where the object struct has a DB member and various methods on it. For now I'm just going to keep adding shit to the DB interface because if it doesn't get too long in the end then it's fine. @@ -26,6 +26,12 @@ type DB interface { EndSession(string) error ClearSessions() error + // General + GetObject(owner, name string) (*Object, error) + + // Defaults + Ensure() error + // Presence ActiveSessions() ([]Session, error) AvatarBySessionID(string) (*Object, error) @@ -38,6 +44,7 @@ type Account struct { ID int Name string Pwhash string + God bool } type Session struct { @@ -70,7 +77,59 @@ func NewDB(connURL string) (DB, error) { return pgdb, nil } +// Ensure checks for and then creates default resources if they do not exist (like the Foyer) +func (db *pgDB) Ensure() error { + // TODO this is sloppy but shrug + _, err := db.pool.Exec(context.Background(), schema) + //log.Println(err) + sysAcc, err := db.GetAccount("system") + if err != nil { + // TODO actually check error. for now assuming it means does not exist + sysAcc, err = db.CreateGod("system", "") + if err != nil { + return fmt.Errorf("failed to create system account: %w", err) + } + } + + log.Printf("%#v", sysAcc) + + if _, err := db.GetObject("system", "foyer"); err != nil { + data := map[string]string{} + data["name"] = "foyer" + data["description"] = "a big room. the ceiling is painted with constellations." + foyer := &Object{ + Data: data, + Script: "", + // TODO default room script + } + if err = db.CreateObject(sysAcc, foyer); err != nil { + return err + } + } + + return nil +} + +func (db *pgDB) CreateGod(name, password string) (account *Account, err error) { + account = &Account{ + Name: name, + Pwhash: password, + God: true, + } + + return account, db.createAccount(account) +} + func (db *pgDB) CreateAccount(name, password string) (account *Account, err error) { + account = &Account{ + Name: name, + Pwhash: password, + } + + return account, db.createAccount(account) +} + +func (db *pgDB) createAccount(account *Account) (err error) { ctx := context.Background() tx, err := db.pool.Begin(ctx) if err != nil { @@ -79,13 +138,8 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro defer tx.Rollback(ctx) - account = &Account{ - Name: name, - Pwhash: password, - } - - stmt := "INSERT INTO accounts (name, pwhash) VALUES ( $1, $2 ) RETURNING id" - err = tx.QueryRow(ctx, stmt, name, password).Scan(&account.ID) + stmt := "INSERT INTO accounts (name, pwhash, god) VALUES ( $1, $2, $3 ) RETURNING id" + err = tx.QueryRow(ctx, stmt, account.Name, account.Pwhash, account.God).Scan(&account.ID) // TODO handle and cleanup unqiue violations if err != nil { return @@ -97,10 +151,11 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro av := &Object{ Avatar: true, Data: data, + Script: "", } - stmt = "INSERT INTO objects ( avatar, data, owner ) VALUES ( $1, $2, $3 ) RETURNING id" - err = tx.QueryRow(ctx, stmt, av.Avatar, av.Data, account.ID).Scan(&av.ID) + stmt = "INSERT INTO objects ( avatar, data, owner, script ) VALUES ( $1, $2, $3, $4 ) RETURNING id" + err = tx.QueryRow(ctx, stmt, av.Avatar, av.Data, account.ID, av.Script).Scan(&av.ID) if err != nil { return } @@ -117,10 +172,11 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro bedroom := &Object{ Bedroom: true, Data: data, + Script: "", } - stmt = "INSERT INTO objects ( bedroom, data, owner ) VALUES ( $1, $2, $3 ) RETURNING id" - err = tx.QueryRow(ctx, stmt, bedroom.Bedroom, bedroom.Data, account.ID).Scan(&bedroom.ID) + stmt = "INSERT INTO objects ( bedroom, data, owner, script ) VALUES ( $1, $2, $3, $4 ) RETURNING id" + err = tx.QueryRow(ctx, stmt, bedroom.Bedroom, bedroom.Data, account.ID, bedroom.Script).Scan(&bedroom.ID) if err != nil { return } @@ -131,9 +187,7 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro return } - err = tx.Commit(ctx) - - return + return tx.Commit(ctx) } func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) { @@ -142,6 +196,10 @@ func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) { return nil, err } + if a.Pwhash == "" { + return nil, errors.New("this account cannot be logged into") + } + // TODO hashing lol if a.Pwhash != password { @@ -276,6 +334,20 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) { return out, nil } +func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) { + ctx := context.Background() + obj = &Object{} + stmt := ` + SELECT id, avatar, data, owner, script + FROM objects + WHERE owner = $1 AND data['name'] = $2` + err = db.pool.QueryRow(ctx, stmt, owner, fmt.Sprintf(`"%s"`, name)).Scan( + &obj.ID, &obj.Avatar, &obj.Data, &obj.OwnerID, &obj.Script) + // TODO i think the escaping here is going to create a sadness ^ + + return +} + func (db *pgDB) ActiveSessions() (out []Session, err error) { stmt := `SELECT id, account FROM sessions` rows, err := db.pool.Query(context.Background(), stmt) @@ -299,6 +371,24 @@ func (db *pgDB) ClearSessions() (err error) { return } +func (db *pgDB) CreateObject(owner *Account, obj *Object) error { + ctx := context.Background() + stmt := ` + INSERT INTO objects (avatar, bedroom, data, script, owner) + VALUES ( $1, $2, $3, $4, $5) + RETURNING id + ` + + err := db.pool.QueryRow(ctx, stmt, + obj.Avatar, obj.Bedroom, obj.Data, obj.Script, owner.ID).Scan( + &obj.ID) + if err != nil { + return err + } + + return nil +} + func randSmell() string { // TODO seeding smells := []string{ diff --git a/server/db/schema.sql b/server/db/schema.sql index 2690c05..77ef624 100644 --- a/server/db/schema.sql +++ b/server/db/schema.sql @@ -17,7 +17,7 @@ CREATE TABLE objects ( avatar boolean NOT NULL DEFAULT FALSE, bedroom boolean NOT NULL DEFAULT FALSE, data jsonb NOT NULL, - script text NOT NULL, + script text NOT NULL, owner integer REFERENCES accounts ON DELETE RESTRICT ); diff --git a/server/witch/witch.go b/server/witch/witch.go index 7e0b8d4..1e14450 100644 --- a/server/witch/witch.go +++ b/server/witch/witch.go @@ -72,7 +72,7 @@ func newScriptContext(obj db.Object) (*scriptContext, error) { return &scriptContext{}, nil } -func (sc *scriptContext) Handle(ver, rest string, sender, target *db.Object) error { +func (sc *scriptContext) Handle(verb, rest string, sender, target *db.Object) error { // TODO call _handle function from the Lstate return nil @@ -82,12 +82,15 @@ type Gateway struct { // maps game object IDs to script contexts m map[int]*scriptContext mu sync.RWMutex + cb func(string, string, *db.Object) } -func NewGateway() *Gateway { +func NewGateway(cb func(string, string, *db.Object)) *Gateway { return &Gateway{ m: map[int]*scriptContext{}, mu: sync.RWMutex{}, + // TODO use cb from scriptContext + cb: cb, } }