Compare commits

..

No commits in common. "84cfc0f12d22c55108bddd88c799c2fa06f61abf" and "9b7e92c118d6f002f5fc1bc54b14ecf43abeb6ab" have entirely different histories.

9 changed files with 134 additions and 500 deletions

11
go.mod
View File

@ -3,7 +3,6 @@ module github.com/vilmibm/hermeticum
go 1.18
require (
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
github.com/jackc/pgx/v4 v4.16.1
github.com/rivo/tview v0.0.0-20220703182358-a13d901d3386
google.golang.org/grpc v1.47.0
@ -12,7 +11,9 @@ require (
require (
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.12.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
@ -23,7 +24,9 @@ require (
github.com/jackc/puddle v1.2.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/yuin/gopher-lua v0.0.0-20221210110428-332342483e3f // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
@ -31,9 +34,3 @@ require (
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)
require (
github.com/google/uuid v1.3.0
github.com/pkg/errors v0.9.1 // indirect
github.com/yuin/gopher-lua v0.0.0-20221210110428-332342483e3f
)

View File

@ -41,63 +41,6 @@ It's tempting to have the `exits` map because of its simplicity, but it actually
TODO draft something new
Coming back to this after a long break, this new scheme with the exits table seems strictly worse than the tildemush approach. i don't like the amount of book keeping in the new approach--that's complexity that can lead to bugs. i think it's ultimately most elegant to just...let the exit exist in two rooms.
aside: i want to think through why exits shouldn't be on a room but it's a pretty quick answer. i don't want rooms that aren't world editable to be un-connectable to other things. if someone comes along and makes room A and then never comes back, it should be tunnel-able to from other rooms. i like the idea of people finding some cobwebbed room and then building a ladder up to it from somewhere.
so i'm going back to the tildemush approach. the next question is; is the exit maps a useful thing? couldn't the go handler just add a second, mirrored go handler? a handler that checks room directionality?
i've added a WITCH function, goes, which takes a direction and two rooms. this WITCH function adds two `go` verb handlers--one for the direction in the `goes` invocation and then one for the reverse. i like this more than the exit map, but it does mean that exits could compete each other. can't remember if that could happen in tildemush (was the exits map stored per exit? was that guarded?).
i think the competing is fine. i'm actually fine with it. i think that goes() could also add an invocation of `provides()` like this:
```lua
provides("use $this", function(args)
move_sender("target_room")
end)
```
A nice idea is the ability for an exit to add flavor to an entity transitioning through it; for this I can maybe add:
```lua
goesAnd(east, "ossuary", "gallery", function(args)
tellSender("the door squeals harshly as you move it but allows you passage")
end)
```
The movement is still generated.
so the problem with all of this is that the beahvior of the exit is
supposedly going to change based on how it is contained. however, we only
re-read the code when the code changes. we're trying to affect game state
as part of code compilation and that's uncool.
a user would create a new exit, put all the finishing touches on it, then move
it from their person to the room the exit originates from. WITCH would not
recompile and no containership would update.
i think the dream of goes(north, "pub") is donezo. i think the next best
thing is goes(north, "foyer", "pub"). on execution, the sender is checked;
if their container is the first room then they are moved to the second
room. both cases suffer from the double containment problem. that second
containment *cannot* be updated as part of WITCH compilation.
so we're back to two options:
- one way exits
- special creation semantics that handle something like double containership
in the interest of moving on--and of putting off special top level commands
that exist outside of WITCH--i want to do one way exits for now. this sucks
because all the flavor written for an exit has to be duplicated for its pair.
some other ideas because i can't let go. what of a variation on the exits map
where each exit stores a key in its has data about where it goes. this is no
better than a dynamic handler (it's worse) and does not help the double
containership problem.
give up and do one way exits.
## server beta
- [x] grpc server

View File

@ -101,19 +101,53 @@ func newServer() (*gameWorldServer, error) {
}
func (s *gameWorldServer) verbHandler(verb, rest string, sender, target db.Object) error {
log.Printf("VH %s %s %d %d", verb, rest, sender.ID, target.ID)
s.scriptsMutex.RLock()
sc, ok := s.scripts[target.ID]
s.scriptsMutex.RUnlock()
var err error
getSend := func(sid string) func(*proto.ClientMessage) error {
return s.msgRouter[sid]
sid, _ := s.db.SessionIDForAvatar(target)
serverAPI := witch.ServerAPI{
Show: func(_ int, _ string) {},
Tell: func(_ int, _ string) {},
DB: func() db.DB {
return s.db
},
}
if sid != "" {
send := s.msgRouter[sid]
getSenderName := func(senderID int) *string {
senderName := "a mysterious stranger"
sender, err := s.db.GetObjectByID(senderID)
if err == nil {
senderName = sender.Data["name"]
} else {
log.Println(err.Error())
}
return &senderName
}
serverAPI.Show = func(senderID int, msg string) {
cm := proto.ClientMessage{
Type: proto.ClientMessage_EMOTE,
Text: msg,
Speaker: getSenderName(senderID),
}
send(&cm)
}
serverAPI.Tell = func(senderID int, msg string) {
cm := proto.ClientMessage{
Type: proto.ClientMessage_OVERHEARD,
Text: msg,
Speaker: getSenderName(senderID),
}
send(&cm)
}
}
if !ok || sc == nil {
if sc, err = witch.NewScriptContext(s.db, getSend); err != nil {
if sc, err = witch.NewScriptContext(serverAPI); err != nil {
return err
}
@ -205,10 +239,6 @@ func (s *gameWorldServer) Commands(stream proto.GameWorld_CommandsServer) error
return s.HandleError(send, err)
}
for _, obj := range affected {
log.Printf("%s heard %s from %d", obj.Data["name"], cmd.Verb, avatar.ID)
}
for _, o = range affected {
if err = s.verbHandler(cmd.Verb, cmd.Rest, *avatar, o); err != nil {
log.Printf("error handling verb %s for object %d: %s", cmd.Verb, o.ID, err)

View File

@ -7,7 +7,6 @@ import (
"fmt"
"log"
"math/rand"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v4/pgxpool"
@ -31,7 +30,6 @@ type DB interface {
// General
GetObject(owner, name string) (*Object, error)
GetObjectByID(ID int) (*Object, error)
SearchObjectsByName(term string) ([]Object, error)
// Defaults
Ensure() error
@ -39,12 +37,10 @@ type DB interface {
// Presence
SessionIDForAvatar(Object) (string, error)
SessionIDForObjID(int) (string, error)
AvatarBySessionID(string) (*Object, error)
BedroomBySessionID(string) (*Object, error)
MoveInto(toMove Object, container Object) error
Earshot(vantage Object) ([]Object, error)
Resolve(vantage Object, term string) ([]Object, error)
Earshot(Object) ([]Object, error)
}
type Account struct {
@ -117,8 +113,6 @@ func (db *pgDB) Ensure() error {
}
}
// TODO for some reason, when the seen() callback runs for foyer we're calling the stub Tell instead of the sid-closured Tell. figure out why.
roomScript := `
seen(function()
tellSender(my("description"))
@ -179,10 +173,10 @@ func (db *pgDB) Ensure() error {
oakDoor = &Object{
Data: data,
Script: `
provides("get tetanus", function(args)
tellSender("you now have tetanus")
go("north", function()
tellMe("the heavy door swings forward with ease. It creaks gently")
moveSender("system", "pub")
end)
goes(north, "pub")
`,
}
if err = db.CreateObject(sysAcc, oakDoor); err != nil {
@ -378,16 +372,6 @@ func (db *pgDB) SessionIDForAvatar(obj Object) (string, error) {
return *sid, nil
}
func (db *pgDB) SessionIDForObjID(id int) (string, error) {
obj, err := db.GetObjectByID(id)
if err != nil {
return "", err
}
return db.SessionIDForAvatar(*obj)
}
func (db *pgDB) AvatarBySessionID(sid string) (avatar *Object, err error) {
avatar = &Object{}
// TODO subquery
@ -442,8 +426,7 @@ func (db *pgDB) Earshot(obj Object) ([]Object, error) {
WHERE id IN (
SELECT contained FROM contains
WHERE container = (
SELECT container FROM contains WHERE contained = $1 LIMIT 1))
OR id = (SELECT container FROM contains WHERE contained = $1 LIMIT 1)`
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
@ -491,54 +474,6 @@ func (db *pgDB) GetObject(owner, name string) (obj *Object, err error) {
return
}
func (db *pgDB) SearchObjectsByName(term string) ([]Object, error) {
ctx := context.Background()
stmt := `
SELECT id, avatar, data, owner, script
FROM objects
WHERE data['name']::varchar LIKE $1
`
rows, err := db.pool.Query(ctx, stmt, "%"+term+"%")
if err != nil {
return nil, err
}
out := []Object{}
for rows.Next() {
o := Object{}
if err = rows.Scan(
&o.ID,
&o.Avatar,
&o.Data,
&o.OwnerID,
&o.Script); err != nil {
return nil, err
}
out = append(out, o)
}
return out, nil
}
func (db *pgDB) Resolve(vantage Object, term string) ([]Object, error) {
stuff, err := db.Earshot(vantage)
if err != nil {
return nil, err
}
out := []Object{}
for _, o := range stuff {
if strings.Contains(o.Data["name"], term) {
out = append(out, o)
}
}
return out, nil
}
func (db *pgDB) GetAccountAvatar(account Account) (*Object, error) {
ctx := context.Background()
obj := &Object{

View File

@ -39,3 +39,9 @@ CREATE TABLE contains (
);
CREATE TYPE heading AS ENUM ('north', 'south', 'east', 'west', 'above', 'below');
CREATE TABLE exits (
startroom integer REFERENCES objects ON DELETE CASCADE,
endroom integer REFERENCES objects ON DELETE CASCADE,
direction heading NOT NULL
);

View File

@ -1,88 +0,0 @@
package witch
import "fmt"
const (
dirEast = "_DIR_EAST"
dirWest = "_DIR_WEST"
dirNorth = "_DIR_NORTH"
dirSouth = "_DIR_SOUTH"
dirAbove = "_DIR_ABOVE"
dirBelow = "_DIR_BELOW"
)
type Direction struct {
raw string
}
func NewDirection(raw string) Direction {
return Direction{raw: raw}
}
func (d Direction) Reverse() Direction {
raw := ""
switch d.raw {
case dirAbove:
raw = dirBelow
case dirBelow:
raw = dirAbove
case dirEast:
raw = dirWest
case dirWest:
raw = dirEast
case dirNorth:
raw = dirSouth
case dirSouth:
raw = dirNorth
}
return NewDirection(raw)
}
// NormalizeDirection takes a direction someone might type like "up" or "north" and returns the correct Direction struct
func NormalizeDirection(humanDir string) (Direction, error) {
raw := ""
switch humanDir {
case "up":
case "above":
raw = dirAbove
case "down":
case "below":
raw = dirBelow
case "east":
raw = dirEast
case "west":
raw = dirWest
case "north":
raw = dirNorth
case "south":
raw = dirSouth
default:
return Direction{}, fmt.Errorf("did not understand direction '%s'", humanDir)
}
return NewDirection(raw), nil
}
// Human returns a string form of this direction like "above" or "north"
func (d Direction) Human() (humanDir string) {
switch d.raw {
case dirAbove:
humanDir = "above"
case dirBelow:
humanDir = "below"
case dirEast:
humanDir = "east"
case dirWest:
humanDir = "west"
case dirNorth:
humanDir = "north"
case dirSouth:
humanDir = "south"
}
return humanDir
}
func (d Direction) Equals(o Direction) bool {
return d.raw == o.raw
}

View File

@ -0,0 +1,62 @@
package witch
import (
lua "github.com/yuin/gopher-lua"
)
func witchHas(l *lua.LState) int {
l.SetGlobal("_has", l.ToTable(1))
return 0
}
// TODO provides
func witchHears(l *lua.LState) int {
return addHandler(l, "say")
}
func witchSees(l *lua.LState) int {
return addHandler(l, "emote")
}
func witchGo(l *lua.LState) int {
// TODO get the handler map
// - check if handler map has a Go handler already, exit early if so
// TODO register this object as an exit in DB
return addHandler(l, "go")
}
func witchSeen(l *lua.LState) int {
return addHandler(l, "look")
}
func witchMy(l *lua.LState) int {
hasT := l.GetGlobal("_has").(*lua.LTable)
val := hasT.RawGetString(l.ToString(1))
l.Push(val)
return 1
}
func witchDoes(ls *lua.LState) int {
// TODO how to feed events back into the server?
// it needs to behave like an event showing up in Commands stream
// this handler needs a reference to the gateway which has a channel for sending events that the server will see?
return 0
}
func addHandler(l *lua.LState, verb string) int {
pattern := l.ToString(1)
cb := l.ToFunction(2)
handlers := l.GetGlobal("_handlers").(*lua.LTable)
verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable)
if !ok {
verbHandlers = l.NewTable()
handlers.RawSetString(verb, verbHandlers)
}
verbHandlers.RawSetString(pattern, cb)
return 0
}

View File

@ -1,18 +1,10 @@
package witch
/*
This file is the interface between the game server and WITCH execution
*/
import (
"fmt"
"log"
"regexp"
"strings"
"github.com/vilmibm/hermeticum/proto"
"github.com/vilmibm/hermeticum/server/db"
lua "github.com/yuin/gopher-lua"
)
@ -31,76 +23,10 @@ end)
`
*/
type serverAPI struct {
db db.DB
getSend func(string) func(*proto.ClientMessage) error
}
func (s *serverAPI) Tell(fromObjID, toObjID int, msg string) {
log.Printf("Tell: %d %d %s", fromObjID, toObjID, msg)
sid, err := s.db.SessionIDForObjID(toObjID)
if err != nil {
log.Println(err)
return
}
if sid == "" {
return
}
from, err := s.db.GetObjectByID(fromObjID)
if err != nil {
log.Println(err)
return
}
speakerName := "an ethereal presence"
if from.Data["name"] != "" {
speakerName = from.Data["name"]
}
log.Println(sid)
send := s.getSend(sid)
cm := proto.ClientMessage{
Type: proto.ClientMessage_OVERHEARD,
Text: msg,
Speaker: &speakerName,
}
send(&cm)
}
func (s *serverAPI) Show(fromObjID, toObjID int, action string) {
sid, err := s.db.SessionIDForObjID(toObjID)
if err != nil {
log.Println(err.Error())
return
}
from, err := s.db.GetObjectByID(fromObjID)
if err != nil {
log.Println(err)
return
}
speakerName := "an ethereal presence"
if from.Data["name"] != "" {
speakerName = from.Data["name"]
}
log.Println(sid)
send := s.getSend(sid)
cm := proto.ClientMessage{
Type: proto.ClientMessage_EMOTE,
Text: action,
Speaker: &speakerName,
}
send(&cm)
}
func (s *serverAPI) DB() db.DB {
return s.db
type ServerAPI struct {
Tell func(int, string)
Show func(int, string)
DB func() db.DB
}
type VerbContext struct {
@ -111,17 +37,14 @@ type VerbContext struct {
}
type ScriptContext struct {
db db.DB
getSend func(*proto.ClientMessage) error
script string
incoming chan VerbContext
serverAPI serverAPI
serverAPI ServerAPI
}
func NewScriptContext(db db.DB, getSend func(string) func(*proto.ClientMessage) error) (*ScriptContext, error) {
func NewScriptContext(sAPI ServerAPI) (*ScriptContext, error) {
sc := &ScriptContext{
serverAPI: serverAPI{db: db, getSend: getSend},
db: db,
serverAPI: sAPI,
}
sc.incoming = make(chan VerbContext)
@ -132,54 +55,25 @@ func NewScriptContext(db db.DB, getSend func(string) func(*proto.ClientMessage)
for {
vc = <-sc.incoming
if vc.Target.Script != sc.script {
// TODO clear this object out of the exits table
sc.script = vc.Target.Script
l = lua.NewState()
// direction constants
l.SetGlobal("east", lua.LString(dirEast))
l.SetGlobal("west", lua.LString(dirWest))
l.SetGlobal("north", lua.LString(dirNorth))
l.SetGlobal("south", lua.LString(dirSouth))
l.SetGlobal("above", lua.LString(dirAbove))
l.SetGlobal("below", lua.LString(dirBelow))
l.SetGlobal("up", lua.LString(dirAbove))
l.SetGlobal("down", lua.LString(dirBelow))
// witch object behavior functions
l.SetGlobal("has", l.NewFunction(sc.wHas))
l.SetGlobal("hears", l.NewFunction(sc.wHears))
l.SetGlobal("sees", l.NewFunction(sc.wSees))
l.SetGlobal("goes", l.NewFunction(sc.wGoes))
l.SetGlobal("seen", l.NewFunction(sc.wSeen))
l.SetGlobal("my", l.NewFunction(sc.wMy))
l.SetGlobal("provides", l.NewFunction(sc.wProvides))
// witch helpers
l.SetGlobal("has", l.NewFunction(witchHas))
l.SetGlobal("hears", l.NewFunction(witchHears))
l.SetGlobal("sees", l.NewFunction(witchSees))
l.SetGlobal("go", l.NewFunction(witchGo))
l.SetGlobal("seen", l.NewFunction(witchSeen))
l.SetGlobal("my", l.NewFunction(witchMy))
l.SetGlobal("_handlers", l.NewTable())
l.SetGlobal("_ID", lua.LNumber(vc.Target.ID))
if err := l.DoString(vc.Target.Script); err != nil {
log.Printf("error parsing script %s: %s", vc.Target.Script, err.Error())
}
}
// witch action functions relative to calling context
l.SetGlobal("tellMe", l.NewFunction(func(l *lua.LState) int {
sender := l.GetGlobal("sender").(*lua.LTable)
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
log.Printf("tellMe: %d %s", senderID, l.ToString(1))
sc.serverAPI.Tell(senderID, vc.Target.ID, l.ToString(1))
return 0
}))
l.SetGlobal("tellSender", l.NewFunction(func(l *lua.LState) int {
sender := l.GetGlobal("sender").(*lua.LTable)
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
log.Printf("tellMe: %d %s", senderID, l.ToString(1))
sc.serverAPI.Tell(vc.Target.ID, senderID, l.ToString(1))
sc.serverAPI.Tell(senderID, l.ToString(1))
return 0
}))
@ -189,7 +83,7 @@ func NewScriptContext(db db.DB, getSend func(string) func(*proto.ClientMessage)
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
owner := l.ToString(1)
name := l.ToString(2)
db := sc.db
db := sc.serverAPI.DB()
senderObj, err := db.GetObjectByID(senderID)
if err != nil {
log.Println(err.Error())
@ -210,33 +104,26 @@ func NewScriptContext(db db.DB, getSend func(string) func(*proto.ClientMessage)
l.SetGlobal("showMe", l.NewFunction(func(l *lua.LState) int {
sender := l.GetGlobal("sender").(*lua.LTable)
senderID := int(lua.LVAsNumber(sender.RawGetString("ID")))
log.Printf("showMe: %d %s", senderID, l.ToString(1))
sc.serverAPI.Show(senderID, vc.Target.ID, l.ToString(1))
sc.serverAPI.Show(senderID, l.ToString(1))
return 0
}))
// TODO showSender?
// TODO check execute permission and bail out potentially
//log.Printf("%#v", vc)
log.Printf("%#v", vc)
senderT := l.NewTable()
senderT.RawSetString("name", lua.LString(vc.Sender.Data["name"]))
senderT.RawSetString("ID", lua.LNumber(vc.Sender.ID))
l.SetGlobal("sender", senderT)
l.SetGlobal("msg", lua.LString(vc.Rest))
l.SetGlobal("_SENDERID", lua.LNumber(vc.Sender.ID))
handlers := l.GetGlobal("_handlers").(*lua.LTable)
handlers.ForEach(func(k, v lua.LValue) {
log.Println("checking handler verbs", k)
if k.String() != vc.Verb {
return
}
v.(*lua.LTable).ForEach(func(kk, vv lua.LValue) {
pattern := regexp.MustCompile(kk.String())
log.Println("checking handler", kk.String(), vv, pattern)
if pattern.MatchString(vc.Rest) {
// TODO TODO TODO TODO TODO
// this could be a remote code execution vuln; but by being here, I
@ -257,126 +144,3 @@ func NewScriptContext(db db.DB, getSend func(string) func(*proto.ClientMessage)
func (sc *ScriptContext) Handle(vc VerbContext) {
sc.incoming <- vc
}
func (sc *ScriptContext) addHandler(l *lua.LState, verb, pattern string, cb *lua.LFunction) {
log.Printf("adding handler: %s %s %#v", verb, string(pattern), cb)
handlers := l.GetGlobal("_handlers").(*lua.LTable)
verbHandlers, ok := handlers.RawGetString(verb).(*lua.LTable)
if !ok {
verbHandlers = l.NewTable()
handlers.RawSetString(verb, verbHandlers)
}
verbHandlers.RawSetString(pattern, cb)
}
func (sc *ScriptContext) wMy(l *lua.LState) int {
hasT := l.GetGlobal("_has").(*lua.LTable)
val := hasT.RawGetString(l.ToString(1))
l.Push(val)
return 1
}
func (sc *ScriptContext) wHas(l *lua.LState) int {
l.SetGlobal("_has", l.ToTable(1))
return 0
}
func (sc *ScriptContext) wHears(l *lua.LState) int {
pattern := l.ToString(1)
cb := l.ToFunction(2)
sc.addHandler(l, "say", pattern, cb)
return 0
}
func (sc *ScriptContext) wSees(l *lua.LState) int {
pattern := l.ToString(1)
cb := l.ToFunction(2)
sc.addHandler(l, "emote", pattern, cb)
return 0
}
func (sc *ScriptContext) wSeen(l *lua.LState) int {
cb := l.ToFunction(1)
sc.addHandler(l, "look", ".*", cb)
return 0
}
func (sc *ScriptContext) wDoes(ls *lua.LState) int {
// TODO how to feed events back into the server?
// it needs to behave like an event showing up in Commands stream
// this handler needs a reference to the gateway which has a channel for sending events that the server will see?
return 0
}
func (sc *ScriptContext) wProvides(l *lua.LState) int {
// TODO test this manually
verbAndPattern := l.ToString(1)
cb := l.ToFunction(2)
split := strings.SplitN(verbAndPattern, " ", 2)
verb := split[0]
pattern := split[1]
sc.addHandler(l, verb, pattern, cb)
return 0
}
func (sc *ScriptContext) wGoes(l *lua.LState) int {
direction := NewDirection(l.ToString(1))
targetRoomTerm := l.ToString(2)
log.Printf("GOT DIRECTION %v", direction)
cb := func(l *lua.LState) (ret int) {
targetRoomList, err := sc.db.SearchObjectsByName(targetRoomTerm)
if err != nil {
log.Printf("failed to search for target room: %s", err.Error())
return
}
switch len(targetRoomList) {
case 0:
log.Printf("failed to find any matching target room. tell player somehow")
return
case 1:
log.Printf("found the target room")
default:
log.Printf("found too many matching target rooms. tell player somehow")
return
}
targetRoom := targetRoomList[0]
msg := l.GetGlobal("msg").String()
normalized, err := NormalizeDirection(msg)
if err != nil {
return
}
sender, err := sc.getSenderFromState(l)
if err != nil {
log.Printf("failed to find sender %s", err.Error())
return
}
if normalized.Equals(direction) {
log.Printf("MOVING SENDER TO '%s'", targetRoom.Data["name"])
// TODO error checking
sc.db.MoveInto(*sender, targetRoom)
sc.serverAPI.Tell(targetRoom.ID, sender.ID, fmt.Sprintf("you are now in %s", targetRoom.Data["name"]))
}
return
}
sc.addHandler(l, "go", ".*", l.NewFunction(cb))
return 0
}
func (sc *ScriptContext) getSenderFromState(l *lua.LState) (*db.Object, error) {
lsID := lua.LVAsNumber(l.GetGlobal("_SENDERID"))
return sc.db.GetObjectByID(int(lsID))
}

View File

@ -159,18 +159,3 @@ provides("give $this $money $unit", function(args)
say("i need more money")
end
end)
-- Example 3: a rusty door
has({
name = "rusty metal door"
description = "it's almost fully consumed by rust but still heavy and solid feeling"
})
allows({
read = "world",
write = "owner"
carry = "owner",
execute = "world",
})
goes("east", "gallery")