messy WIP; default objects + witch stuff

trunk
vilmibm 2022-12-22 22:03:37 -08:00
parent 89e4c4b095
commit 1664d678cb
4 changed files with 156 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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