messy WIP; default objects + witch stuff
parent
89e4c4b095
commit
1664d678cb
|
@ -84,6 +84,10 @@ func newServer() (*gameWorldServer, error) {
|
||||||
return nil, err
|
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 {
|
if err = db.ClearSessions(); err != nil {
|
||||||
return nil, fmt.Errorf("could not clear sessions: %w", err)
|
return nil, fmt.Errorf("could not clear sessions: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -91,12 +95,17 @@ func newServer() (*gameWorldServer, error) {
|
||||||
s := &gameWorldServer{
|
s := &gameWorldServer{
|
||||||
msgRouter: make(map[string]func(*proto.ClientMessage) error),
|
msgRouter: make(map[string]func(*proto.ClientMessage) error),
|
||||||
db: db,
|
db: db,
|
||||||
Gateway: witch.NewGateway(),
|
|
||||||
}
|
}
|
||||||
|
gw := witch.NewGateway(s.HandleCmd)
|
||||||
|
s.Gateway = gw
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *gameWorldServer) HandleCmd(verb, rest string, sender *db.Object) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error {
|
func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error {
|
||||||
var sid string
|
var sid string
|
||||||
for {
|
for {
|
||||||
|
@ -129,20 +138,26 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
|
||||||
}
|
}
|
||||||
log.Printf("found avatar %#v", avatar)
|
log.Printf("found avatar %#v", avatar)
|
||||||
|
|
||||||
switch cmd.Verb {
|
s.HandleCmd(cmd.Verb, cmd.Rest, avatar)
|
||||||
case "say":
|
|
||||||
if err = s.HandleSay(avatar, cmd.Rest); err != nil {
|
/*
|
||||||
s.HandleError(func(_ *proto.ClientMessage) error { return nil }, err)
|
|
||||||
|
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)
|
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 {
|
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 = s.db.MoveInto(*av, *foyer); err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("failed to move %d into %d: %w", av.ID, foyer.ID, err)
|
||||||
return nil, fmt.Errorf("failed to move %d into %d: %w", av.ID, bedroom.ID, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO send room info, avatar info to client (need to figure this out and update proto)
|
// TODO send room info, avatar info to client (need to figure this out and update proto)
|
||||||
|
|
120
server/db/db.go
120
server/db/db.go
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// go:embed schema.sql
|
//go:embed schema.sql
|
||||||
var schema string
|
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.
|
// 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
|
EndSession(string) error
|
||||||
ClearSessions() error
|
ClearSessions() error
|
||||||
|
|
||||||
|
// General
|
||||||
|
GetObject(owner, name string) (*Object, error)
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
Ensure() error
|
||||||
|
|
||||||
// Presence
|
// Presence
|
||||||
ActiveSessions() ([]Session, error)
|
ActiveSessions() ([]Session, error)
|
||||||
AvatarBySessionID(string) (*Object, error)
|
AvatarBySessionID(string) (*Object, error)
|
||||||
|
@ -38,6 +44,7 @@ type Account struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
Pwhash string
|
Pwhash string
|
||||||
|
God bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -70,7 +77,59 @@ func NewDB(connURL string) (DB, error) {
|
||||||
return pgdb, nil
|
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) {
|
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()
|
ctx := context.Background()
|
||||||
tx, err := db.pool.Begin(ctx)
|
tx, err := db.pool.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -79,13 +138,8 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro
|
||||||
|
|
||||||
defer tx.Rollback(ctx)
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
account = &Account{
|
stmt := "INSERT INTO accounts (name, pwhash, god) VALUES ( $1, $2, $3 ) RETURNING id"
|
||||||
Name: name,
|
err = tx.QueryRow(ctx, stmt, account.Name, account.Pwhash, account.God).Scan(&account.ID)
|
||||||
Pwhash: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt := "INSERT INTO accounts (name, pwhash) VALUES ( $1, $2 ) RETURNING id"
|
|
||||||
err = tx.QueryRow(ctx, stmt, name, password).Scan(&account.ID)
|
|
||||||
// TODO handle and cleanup unqiue violations
|
// TODO handle and cleanup unqiue violations
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -97,10 +151,11 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro
|
||||||
av := &Object{
|
av := &Object{
|
||||||
Avatar: true,
|
Avatar: true,
|
||||||
Data: data,
|
Data: data,
|
||||||
|
Script: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt = "INSERT INTO objects ( avatar, data, owner ) VALUES ( $1, $2, $3 ) RETURNING 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).Scan(&av.ID)
|
err = tx.QueryRow(ctx, stmt, av.Avatar, av.Data, account.ID, av.Script).Scan(&av.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -117,10 +172,11 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro
|
||||||
bedroom := &Object{
|
bedroom := &Object{
|
||||||
Bedroom: true,
|
Bedroom: true,
|
||||||
Data: data,
|
Data: data,
|
||||||
|
Script: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt = "INSERT INTO objects ( bedroom, data, owner ) VALUES ( $1, $2, $3 ) RETURNING 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).Scan(&bedroom.ID)
|
err = tx.QueryRow(ctx, stmt, bedroom.Bedroom, bedroom.Data, account.ID, bedroom.Script).Scan(&bedroom.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -131,9 +187,7 @@ func (db *pgDB) CreateAccount(name, password string) (account *Account, err erro
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit(ctx)
|
return tx.Commit(ctx)
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *pgDB) ValidateCredentials(name, password string) (*Account, error) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.Pwhash == "" {
|
||||||
|
return nil, errors.New("this account cannot be logged into")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO hashing lol
|
// TODO hashing lol
|
||||||
|
|
||||||
if a.Pwhash != password {
|
if a.Pwhash != password {
|
||||||
|
@ -276,6 +334,20 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
|
||||||
return out, nil
|
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) {
|
func (db *pgDB) ActiveSessions() (out []Session, err error) {
|
||||||
stmt := `SELECT id, account FROM sessions`
|
stmt := `SELECT id, account FROM sessions`
|
||||||
rows, err := db.pool.Query(context.Background(), stmt)
|
rows, err := db.pool.Query(context.Background(), stmt)
|
||||||
|
@ -299,6 +371,24 @@ func (db *pgDB) ClearSessions() (err error) {
|
||||||
return
|
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 {
|
func randSmell() string {
|
||||||
// TODO seeding
|
// TODO seeding
|
||||||
smells := []string{
|
smells := []string{
|
||||||
|
|
|
@ -17,7 +17,7 @@ CREATE TABLE objects (
|
||||||
avatar boolean NOT NULL DEFAULT FALSE,
|
avatar boolean NOT NULL DEFAULT FALSE,
|
||||||
bedroom boolean NOT NULL DEFAULT FALSE,
|
bedroom boolean NOT NULL DEFAULT FALSE,
|
||||||
data jsonb NOT NULL,
|
data jsonb NOT NULL,
|
||||||
script text NOT NULL,
|
script text NOT NULL,
|
||||||
|
|
||||||
owner integer REFERENCES accounts ON DELETE RESTRICT
|
owner integer REFERENCES accounts ON DELETE RESTRICT
|
||||||
);
|
);
|
||||||
|
|
|
@ -72,7 +72,7 @@ func newScriptContext(obj db.Object) (*scriptContext, error) {
|
||||||
return &scriptContext{}, nil
|
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
|
// TODO call _handle function from the Lstate
|
||||||
return nil
|
return nil
|
||||||
|
@ -82,12 +82,15 @@ type Gateway struct {
|
||||||
// maps game object IDs to script contexts
|
// maps game object IDs to script contexts
|
||||||
m map[int]*scriptContext
|
m map[int]*scriptContext
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
cb func(string, string, *db.Object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGateway() *Gateway {
|
func NewGateway(cb func(string, string, *db.Object)) *Gateway {
|
||||||
return &Gateway{
|
return &Gateway{
|
||||||
m: map[int]*scriptContext{},
|
m: map[int]*scriptContext{},
|
||||||
mu: sync.RWMutex{},
|
mu: sync.RWMutex{},
|
||||||
|
// TODO use cb from scriptContext
|
||||||
|
cb: cb,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue