WIP very sleepy
parent
76382a2b36
commit
7413b12b4a
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/vilmibm/hermeticum/proto"
|
"github.com/vilmibm/hermeticum/proto"
|
||||||
"github.com/vilmibm/hermeticum/server/db"
|
"github.com/vilmibm/hermeticum/server/db"
|
||||||
|
"github.com/vilmibm/hermeticum/server/witch"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ type gameWorldServer struct {
|
||||||
db db.DB
|
db db.DB
|
||||||
mu sync.Mutex // for msgRouter
|
mu sync.Mutex // for msgRouter
|
||||||
msgRouter map[string]func(*proto.ClientMessage) error
|
msgRouter map[string]func(*proto.ClientMessage) error
|
||||||
|
Gateway *witch.Gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServer() (*gameWorldServer, error) {
|
func newServer() (*gameWorldServer, error) {
|
||||||
|
@ -89,6 +91,7 @@ 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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -251,20 +254,20 @@ func (s *gameWorldServer) Login(ctx context.Context, auth *proto.AuthInfo) (si *
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *gameWorldServer) HandleSay(avatar *db.Object, msg string) error {
|
func (s *gameWorldServer) HandleSay(sender *db.Object, msg string) error {
|
||||||
name := avatar.Data["name"]
|
name := sender.Data["name"]
|
||||||
if name == "" {
|
if name == "" {
|
||||||
// TODO determine this based on a hash or something
|
// TODO determine this based on a hash or something
|
||||||
name = "a mysterious figure"
|
name = "a mysterious figure"
|
||||||
}
|
}
|
||||||
|
|
||||||
heard, err := s.db.Earshot(*avatar)
|
heard, err := s.db.Earshot(*sender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("found %#v in earshot of %#v\n", heard, avatar)
|
log.Printf("found %#v in earshot of %#v\n", heard, sender)
|
||||||
|
|
||||||
as, err := s.db.ActiveSessions()
|
as, err := s.db.ActiveSessions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -274,6 +277,7 @@ func (s *gameWorldServer) HandleSay(avatar *db.Object, msg string) error {
|
||||||
sendErrs := []error{}
|
sendErrs := []error{}
|
||||||
|
|
||||||
for _, h := range heard {
|
for _, h := range heard {
|
||||||
|
s.Gateway.VerbHandler(msg, *sender, h)
|
||||||
// TODO once we have a script engine, deliver the HEARS event
|
// TODO once we have a script engine, deliver the HEARS event
|
||||||
for _, sess := range as {
|
for _, sess := range as {
|
||||||
if sess.AccountID == h.OwnerID {
|
if sess.AccountID == h.OwnerID {
|
||||||
|
|
|
@ -51,6 +51,7 @@ type Object struct {
|
||||||
Bedroom bool
|
Bedroom bool
|
||||||
Data map[string]string
|
Data map[string]string
|
||||||
OwnerID int
|
OwnerID int
|
||||||
|
Script string
|
||||||
}
|
}
|
||||||
|
|
||||||
type pgDB struct {
|
type pgDB struct {
|
||||||
|
@ -203,11 +204,11 @@ func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
|
||||||
|
|
||||||
// TODO subquery
|
// TODO subquery
|
||||||
stmt := `
|
stmt := `
|
||||||
SELECT id, avatar, data, owner
|
SELECT id, avatar, data, owner, script
|
||||||
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.OwnerID)
|
&avatar.ID, &avatar.Avatar, &avatar.Data, &avatar.OwnerID, &avatar.Script)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +250,7 @@ func (db *pgDB) MoveInto(toMove Object, container Object) error {
|
||||||
|
|
||||||
func (db *pgDB) Earshot(obj Object) ([]Object, error) {
|
func (db *pgDB) Earshot(obj Object) ([]Object, error) {
|
||||||
stmt := `
|
stmt := `
|
||||||
SELECT id, avatar, bedroom, data, owner FROM objects
|
SELECT id, avatar, bedroom, data, owner, script FROM objects
|
||||||
WHERE id IN (
|
WHERE id IN (
|
||||||
SELECT contained FROM contains
|
SELECT contained FROM contains
|
||||||
WHERE container = (
|
WHERE container = (
|
||||||
|
@ -263,7 +264,10 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
|
||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
heard := Object{}
|
heard := Object{}
|
||||||
if err = rows.Scan(&heard.ID, &heard.Avatar, &heard.Bedroom, &heard.Data, &heard.OwnerID); err != nil {
|
if err = rows.Scan(
|
||||||
|
&heard.ID, &heard.Avatar,
|
||||||
|
&heard.Bedroom, &heard.Data,
|
||||||
|
&heard.OwnerID, &heard.Script); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = append(out, heard)
|
out = append(out, heard)
|
||||||
|
|
|
@ -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,
|
script text NOT NULL,
|
||||||
|
|
||||||
owner integer REFERENCES accounts ON DELETE RESTRICT
|
owner integer REFERENCES accounts ON DELETE RESTRICT
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package witch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/vilmibm/hermeticum/server/db"
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
func hasWrapper(obj db.Object) func(*lua.LState) int {
|
||||||
|
return func(ls *lua.LState) int {
|
||||||
|
lv := ls.ToTable(1)
|
||||||
|
log.Printf("%#v", lv)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hearsWrapper(obj db.Object) func(*lua.LState) int {
|
||||||
|
return func(ls *lua.LState) int {
|
||||||
|
// TODO get handler from _handlers
|
||||||
|
// TODO call it
|
||||||
|
// TODO how to get message in here?
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func does(ls *lua.LState) int {
|
||||||
|
// TODO
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const addHandler = `
|
||||||
|
_addHandler = function(verb, pattern, cb)
|
||||||
|
_handlers[verb] = function(message)
|
||||||
|
f, l = string.find(message, pattern)
|
||||||
|
if f != nil
|
||||||
|
cb(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`
|
|
@ -0,0 +1,131 @@
|
||||||
|
package witch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/vilmibm/hermeticum/server/db"
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
the purpose of this package is to provide abstractions for sending verbs to game objects.
|
||||||
|
|
||||||
|
Game objects get pulled from the DB into memory and their scripts become Lua States.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
type scriptContext struct {
|
||||||
|
script string
|
||||||
|
l *lua.LState
|
||||||
|
incoming chan string
|
||||||
|
// TODO whatever is needed to support calling a Go API
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *scriptContext) NeedsRefresh(obj db.Object) bool {
|
||||||
|
return sc.script != obj.Script
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO using a dummy script for now
|
||||||
|
|
||||||
|
const dummyScript = `
|
||||||
|
has({
|
||||||
|
name = "spaghetti",
|
||||||
|
description = "a plate of pasta covered in pomodoro sauce"
|
||||||
|
})
|
||||||
|
|
||||||
|
hears(".*eat.*", function(msg)
|
||||||
|
does("quivers nervously")
|
||||||
|
end)
|
||||||
|
`
|
||||||
|
|
||||||
|
/*
|
||||||
|
allows({
|
||||||
|
read = "world",
|
||||||
|
write = "owner"
|
||||||
|
carry = "owner",
|
||||||
|
execute = "world",
|
||||||
|
})
|
||||||
|
|
||||||
|
hears(".*eat.*", function(msg)
|
||||||
|
does("quivers nervously")
|
||||||
|
end)
|
||||||
|
`
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO figure out channel stuff
|
||||||
|
// TODO figure out how to inject WITCH header
|
||||||
|
// - do i inject from Go or prepend some Lua code?
|
||||||
|
// TODO figure out how the Lua code can affect Go and thus the database
|
||||||
|
|
||||||
|
func newScriptContext(obj db.Object) (*scriptContext, error) {
|
||||||
|
l := lua.NewState()
|
||||||
|
|
||||||
|
l.SetGlobal("has", l.NewFunction(hasWrapper(obj)))
|
||||||
|
l.SetGlobal("_handlers", l.NewTable())
|
||||||
|
|
||||||
|
//if err := l.DoString(obj.Script); err != nil {
|
||||||
|
if err := l.DoString(dummyScript); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &scriptContext{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Gateway struct {
|
||||||
|
// maps game object IDs to script contexts
|
||||||
|
m map[int]*scriptContext
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGateway() *Gateway {
|
||||||
|
return &Gateway{
|
||||||
|
m: map[int]*scriptContext{},
|
||||||
|
mu: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshObj ensures that the script context for the given game object is running the latest code for the object's script
|
||||||
|
func (g *Gateway) RefreshObject(obj db.Object) error {
|
||||||
|
g.mu.RLock()
|
||||||
|
var sc *scriptContext
|
||||||
|
|
||||||
|
if sc, ok := g.m[obj.ID]; ok {
|
||||||
|
if !sc.NeedsRefresh(obj) {
|
||||||
|
g.mu.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.mu.RUnlock()
|
||||||
|
|
||||||
|
sc, err := newScriptContext(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
g.m[obj.ID] = sc
|
||||||
|
g.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Gateway) VerbHandler(msg string, sender, target db.Object) error {
|
||||||
|
var sc *scriptContext
|
||||||
|
g.mu.RLock()
|
||||||
|
sc, ok := g.m[target.ID]
|
||||||
|
g.mu.RUnlock()
|
||||||
|
|
||||||
|
if !ok || sc.NeedsRefresh(target) {
|
||||||
|
sc, err := newScriptContext(target)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
g.m[target.ID] = sc
|
||||||
|
g.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue