Room scripting

A very clever wolf has persistently been asking me to implement scripting.
It is one of those features I’ve always planned to add eventually. And maybe now is a good time.

But it is a big piece that I need your help with on how to structure it and limit it.
@farcaller has already started helping me with ideas in this post: Simple scripting implementation idea

To limit the scope a bit, my first aim is this:

To be able to implement the Switches found at the Foxhole

In short; to allow a character to use a “switch” in one room, that effects other rooms. For more details, go to Foxhole and try to figure things out! :slight_smile:

The scope means, I will not focus on exit events, allowing for making lockable exits, or global functionality as in farcaller’s suggestion to help create bankers. For now, let’s focus on room scripts only.

Requirements

It should be possible to, in a room, to add commands such as turn on switch and turn off switch, which can be used to:

  • output a descriptive message in current room or other rooms
  • change the settings (eg. profile) of the current room or other rooms

It should exist a way to list room-specific commands.
These requirements are just for this first test-implementation. More functionality will be added later.

Custom command registrations

Rooms scripts should be able to add and remove custom commands. The commands should be described with some json structure. With pseudo-js code, it could look like this:

room.addCommand({
   cmd: "useSwitch",
   steps: [
      "turn",
      { type: "select", options: [ "on", "off" ] },
      "switch",
   ],
});

When used, a command event will be triggered on the script.

In a similar way, room scripts may remove commands.
Adding/removing commands may be done live.
Built in commands have a higher priority than custom commands. So a room may not override the built-in say command.

Room events

Apart from custom commands, room scripts should also be able to listen to other events such as charEnter, charLeave, charWakeup, and charSleep.
Room scripts MAY listen to communication events (whisper, say, pose, ooc, describe), but in such a case, the characters in the room should have an indicator showing that they are being listened to/recorded. This is in order to ensure integrity, and to avoid a false sense of privacy when scripts are listening.

Centralized or decentralized scripts

Here is one of my big questions regarding scripts. I see two different approaches:

  • Centralized scripts - Allow creation of a single script that may be able to control multiple rooms. For such a solution, a single script can add a switch in one room, and edit another room when the switch is used. But for this to work, a script must have access permission for the different rooms it may control.

  • Decentralized scripts - Alternatively, a room has a script. Only the owner (or a builder) may add a script to a room. For scripts to work across multiple rooms, scripts should have ways of sending messages to other scripts. It is up to the receiving script if it acts on that message. For such a solution, the room with the switch will have one script that sends a message to the target-room’s script to tell it to change the profile.

The decentralized approach be more work to manage. But I think it would simplify the permission structure, as “permission” is just based on whether a room’s script cares about messages from others.

API vs server-side VM

Where should scripts run?

For Centralized scripts, an API approach might be simplest. This means that scripts can be written in any language and be running on third party computers. Very much like bot-scripts. For such scripts, we could create JWT access tokens which is used by the script when connecting to the API. The token would tell which rooms/etc. the script has access to, and the script would be allow to subscribe to any/all of those rooms.

For Decentralized scripts, it would probably be easier to run them in some LUA VMs on the server. This means you would have to upload scripts (or have an in-game editor?), and have a harder time debugging a more complex system. But it would also lower the risk of having rooms stop functioning because of some external script being disconnected. And you don’t actually have to have a script server running all the time.

Okay… Those are some of my thoughts. I am a bit sleep now,
Please, help me think!

1 Like

So with all of this in mind, I do have a couple of thoughts.

Privacy

The thing that comes to my immediate thoughts for this concern here:

is to have another header in the room panel, somewhere around where the exits and room population are situated s list of available scripts and an indicator on if they’re hooked into listening in on conversations. That’ll also solve the potential issue of patrons not knowing that a script is even available in a room, and potentially work as a gui button for summoning switches and buttons without having to type commands. And would also fulfill the requirement:

Centralization

I think the decentralized approach, while potentially more complicated, would allow for better cooperation from different builders. So if that’s something we want to keep focus on, I think it would probably end up as the better option in the long run. Otherwise, I’d probably say to keep things simpler for solo-builds and go for the centralized approach.

API v Server-side VM

I may just be here to cause chaos, but I’m all for having both options available, heheh. I think having both available for different use cases and developer proficiencies will go along way for the overall ecosystem of scripting beyond the initial scope proposed at this time. That being said, for the current requirements, I think using server-side scripting will lead to a more expedient implementation. An in-game editor using CodeMirror would be especially helpful for accessibility. And hey, maybe sometime down the line some sort of vscode integration for debugging server-side scripts on the test site could be in the cards. Pipe dream, I know. But who knows, heheheh.

Anyways, I think for a first go, accessibility should be a focus, so I’m all for the server-side scripting. Then as the features develop and potentially the possibility for complexity increases, expand to an API based, externally hosted option like the bots have, maybe even as a supporter-first feature, could then be worked on.

Well those are my ramblings on the topic, if you want some help in any leg work development, I can’t guarantee a lot of help, but I’m willing to lend a hand anyway :grin:

the other rooms would be limited to desc I guess?

I’d think more flexibility (i.e. a prefix with a free-form followup text) would work better, but if those are discoverable it’s a non-concern. If they are not easily discoverable, you’d want a bunch of options for the same action. Here’s some regex-smeared code I had to do following @Shinyuu’s testing on people.

I lean towards this being a more reasonable idea, but I just like the actor pattern. It feels easier to manage, too. in the end you just subscribe to another event being “script call” and then you’re just doing RPC. I can surely see how that can go and bite you in the ass with loops and whatnot, but it’s a general concern with the scripts being non-deterministic

I have a very different opinion from the previous comment. Do the API first because the infrastructure is there and it allows you to decouple what scripts can do and how scripts work. In the end, you can use the same exact API to then run your script host on your server with a nice UI exposed.

Your primary concern shouldn’t be with how to host scripts or how to make an usable UI to edit them. Instead focus on how scripts access and mutate the world, what happens if a script goes rogue, what if two scripts start fighting in a loop. Having a local VM only adds the UI and the execution time concerns so that people won’t mine bitcoins on wolfery. If you can only do the scripting magic via the pubic API it allows you to better define that API no matter where the script lives. If you then want to add a VM hosting and a debug log and a vscode extension, that’d just be part of your extra offering on top of what scripts can do.

Why not port MPI instead of re-inventing the cheese wheel?

I would guess that would be because Acci would have to write the whole engine himself in a language that’s compatible with the server, whereas lua interpreters already exist and just need to be integrated. The same issues would need to be addressed either way, and using a modern scripting language instead of the rather obscure MPI syntax would be a much simpler task which would free Acci up to work on other necessary features.

1 Like

As Vernon said; it would be a major effort to port MPI to Go (which is what the backend is written in), and the benefits of using a more established scripting language like LUA would have multiple other benefits. And I am not even sure MPI would even be a good fit.

But that said; while LUA (or similar) may be chosen for the scripts running server-side, I still believe we need some type of template language to format output. MPI is perhaps overkill for that.

What I mean is; we would also want a syntax to help us format text based on things like grammatical gender, variable placeholders (like inserting character names), or perhaps create variety in output with randomized alternatives.

Example of a grammatically gender aware travel message using ICU Message Syntax

heads down the forest path. Not before long, {gender, select,
    male {he reaches}
    female {she reaches}
    neutral {it reaches}
    other {they reach}
} a small clearing.

(Yes, I am aware that “heads” also should also be affected by gender)

I think Mucklet, to put a fine point on it, has an opportunity to ditch a significant amount of legacy tech debt.

Mucks are stuck with two scripting languages which I usually describe as ‘a snapshot of the two most popular university research languages in 1989’. Many modern users would be happy with a Lua-oid and are going to nope out at ‘weird Lisp’.

(upon further reflection) We’d also have to precisely replicate the object and user models for it to make sense - i.e. actually being MPI/MUF rather than ‘Wolfery’s weird version of…’ so it might not be worth the effort anyway.

2 Likes

I’m imagining a light-switch outside of the room which could be toggled purely to childishly annoy the folks inside of the room x3

You mean a golden opportunity for a suspiciously short cheetah inside to have an impromptu rave? :stuck_out_tongue_winking_eye:

1 Like

The Cheat! We had that light switch installed so you can turn the lights on and off, not so you can have light switch raves.

–Strong Bad

2 Likes

I can always use the room profile to set up the mood. If I want to have flying fish (for some reason), I can change the profile.

image

While I haven’t been socializing so much, I have at least been tinkering.
And I’ve finally taken on the beast that is Room scripting!

First attempt was to use Lua as scripting language. But I had to scrap that approach after admitting to myself that I couldn’t fully prevent scripts from eating up all the server’s memory.

I did consider using Javascript running in V8 isolates instead. But in the end, I decided to test a suggestion from @farcaller, using WebAssembly.

WebAssembly, primarily meant for browsers, could work on the server too, allowing scripts to run in a fully sandboxed and secure environment, with high efficiency.

As a scripting language, I selected AssemblyScript. It is a young language, still rough around the edges. But it compiles to small WebAssembly-files with very little overhead, and is similar to TypeScript. So, I would have scripts as WebAssembly files that would be quick to load and execute.

I am still midwork in this, but I thought I’d show how it might look.

Cabin intercom example

This is an example of an intercom script that allows someone within Carpentry Cabin to turn on the intercom to speak with those outside:

// Address to the room script running inside the cabin
const cabin = "room.c2d1ml0t874bj4eva85g#cr82u0kks8o5ku4vh7eg"

// init is called when the script is created/updated and allows the script
// to start listening to room events, and add other hooks.
export function init(): void {
    // Explicitly allow the cabin script to send messages here.
    // Other scripts will be ignored.
    Script.listen([cabin])
}

// onMessage is called when a script posts a message to this script.
// In this case, the messages are posted from the script inside the cabin.
export function onMessage(addr: string, topic: string, dta: string): void {
    if (topic == "on") { // Turn on intercom
        // Start listening to room events. This will be indicated to the players
        // in the room to let them know that a script is "listening" to them.
        Room.listen()
        Room.describe("A static sound is heard from a speaker as a red light turns on, indicating recording.")
    }
    if (topic == "off") { // Turn off intercom
        Room.unlisten()
        Room.describe("The speaker goes silent, and the red light fades out.")
    }
    if (topic == "event") { // Receive an event over the intercom
        if (Event.getType(dta) == "say") {
            let say = JSON.parse<Event.Say>(dta)
            Room.describe(`**${say.char ? say.char!.name : ""}** says through the speaker, "${say.msg}"`)
        }
    }
}

// onRoomEvent is called when an event happens in this room
export function onRoomEvent(addr: string, evjson: string): void {
    // Post the event as a message to the room script inside the cabin.
    Script.post(cabin, "event", evjson)
}

The result can look like this:

image

Neat, right? I felt I wanted to share.

6 Likes