drwhut / tabletop-club

An open-source platform for playing tabletop games in a physics-based 3D environment for Windows, macOS, and Linux! Made with the Godot Engine.
https://tabletopclub.net
MIT License
1.27k stars 55 forks source link

Add a Lua scripting API. #65

Open traverseda opened 2 years ago

traverseda commented 2 years ago

Is lua scripting a planned feature? I know that tabletop simulator has a pretty complete api, in a perfect world tabletop-club could be API-compatible with tabletop-simulator? There are already a bunch of good resources for tabletop simulator API stuff.

drwhut commented 2 years ago

That was a plan for much later down the line - right now I'm not sure if there is enough "demand" for such a feature, and plus since it involves scripting I would have to make sure that the Lua environment would be as secure as possible.

I'll have a look into Tabletop Simulator's API, but I highly doubt I would be able to replicate their API, at least fully. The game has its own "pack" directory system, I didn't look into how TS handles mods at all when creating the pack system for this game.

traverseda commented 2 years ago

I've looked into tabletop simulators mod system a fair bit. It's very inspired by the web, basically each mod is a "document" (a json file) that links to other resources. All assets are loaded from the web, from urls.

Basically each mod just links to assets that are available somewhere in the wild.

More complex assets use unity asset packs, generally that's limited to 3D models only though. Most mods consist of a json file saying what objects exist where, and where you can download the assets, normally as PNG or JPEG images.

That means that is someone gets that json file you don't need to go to steam to get the rest of the assets, you just go to the web and download the assets. Even just a utility that goes through and extracts all the decks/boards from a TTS mod could be very useful.

traverseda commented 2 years ago

I've worked with the TTS mod format in the past in order to back-up mod data, it's basically just a json file with the current board state serialized to it. Presuming that tabletop-club has similar functionality it should be reasonable to use TTS mods. The lua API is what would be the hardest to translate. Translating colors, piece locations, etc should all be reasonably doable.

I don't particularly like TTS's API or feature set, but there is a lot of easily accessible content if you go that route, and it's not as locked behind steam as you'd imagine.

Cluedrew commented 1 year ago

Have you considered using GDScript, or a variant of GDScript, as the scripting language? I had toyed with the idea in my "what if I remade Tabletop Simulator" musings. It has great integration with the underlying engine of Godot, has hooks for interacting with a GUI layer on top of it and you might be able to copy a lot of the code you need from the Godot Editor. Unfortunately, time is limited and I am also contributing to another open source game making tool (the Solarus Engine) so I never got any further than that. So this is just putting the idea out there.

By the way the Solarus Engine uses Lua (straight Lua, no modifications) as its scripting extension and I can say that has worked great. I just ask that, whatever your choice, you have some sort of namespacing/module system so not all the global code has to be in a single hilariously large file.

drwhut commented 1 year ago

Yeah, the idea to use GDScript has crossed my mind at some point - it would be a lot easier to implement than Lua, that much is for sure, but in terms of accessibility I think Lua has the edge of being more popular (purely because GDScript was specifically designed for Godot). On the other hand, Python is a lot more popular than Lua, and GDScript's syntax is closer to that of Python's, so one could also make the argument that GDScript would be more accessible.

The way I currently see it, there are three options to add a scripting API to the game: GDScript only, Lua only, or both. If we just include one language, then we'll essentially force users to learn another language if they don't already happen to know the one that is implemented. However, we'd be able to provide better support for that one language. If we include both languages, then that decreases the "height of the wall" that users have to climb in order to start scripting, but this would also increase the development time, and effort required to support both languages. Plus, what's to stop us from implementing a third, or even a fourth language?

I would love to hear other's opinions on this, since I'm somewhat torn as to which language(s) the scripting API should use.

I just ask that, whatever your choice, you have some sort of namespacing/module system so not all the global code has to be in a single hilariously large file.

Do you mean for the API implementation, or for the scripts themselves? Either way, I will try my best :grin:

traverseda commented 1 year ago

I'm not aware of any current GDScript sandboxing. https://github.com/godotengine/godot-proposals/issues/5010 for more.

So if you want to load arbitrary game code from the web like tabletop sim does it can't be gdscript yet.

Cluedrew commented 1 year ago

Note that Lua doesn't offer sandboxing out of the box either (Tabletop Simulator's Lua is very modified and restricted to accomplish that), but there are probably more resources out there to accomplish that. Taking some functionality out might be easier than fully sandboxing everything. There are many features needed a full programming language you do not need here.

But some version of require (Lua) or load (GDScript) might be helpful though. That is because, although spitting up both the API implementation and the scripts might be helpful, I was talking about the scripts. I did want to contribute to one of my favourite Tabletop Simulator mods, but its global script is over 7000 lines long. I got in one small improvement and it took a lot of searching. So having one main global script (that is loaded and run on game start) and any number of utility scripts you can import from the main script (and any object scripts or other utility scripts) would help organize any medium sized or larger project.

drwhut commented 1 year ago

I haven't fully thought through how the scripts would be structured, but my initial thought was that there would be one "global script" for each asset pack which has callbacks for things like entering a game, or someone joining, as well as scripts in the various sub-folders that can be assigned to the objects in an asset pack, allowing for more functionality. Which I think at least helps with splitting up the code like you suggested.

I've dabbled a little bit in Lua in various projects before, and I already had an idea as to how to "sandbox" the scripts by removing elements of the global table (and potentially allowing them to be added back in using a flag which warns the player when it is used, but I'm still thinking about that part), but if there isn't a way to sandbox GDScript as of right now, then that rules it out as a scripting language.

poVoq commented 1 year ago

https://github.com/WeaselGames/godot_luaAPI

drwhut commented 1 year ago

Ah interesting, I was not aware there was an alternative to perbone/luascript, will have to compare the two at some point.

poVoq commented 1 year ago

Luascript is aiming to offer an alternative for GDscript, like the C# option in Godot, but with lua.

The one I linked is exactly what this issue is asking for: a sandboxed lua API on top of a GDscript game for easy modding in lua.

Cluedrew commented 1 year ago

I went quiet because people were figuring it out and I don't want to add extra comments, but since I've got one new thing to add I think I will start with a bit of stray comments on the script organization.

Having a global script and individual scripts are pretty standard, and for fairly obvious reasons. The type of script I would ask to add is a utility/module script that on its own does nothing, but can be required/imported from other scripts (when you remove global functions for sandboxing, a custom require might be needed). These are wrapped in with the game room or asset pack or whatever fits with the rest of the design of Tabletop Club really well.

Anyways, one more request, don't know if it will be the last, which was inspired by GDScript but I don't actually know if a sandboxed version could pull off: Exported Variables! With honest to goodness code reuse having an easy way to configure things from the GUI editor (probably near were you go to set/edit the script) might be nice. Not really essential though.

But it sounds like there is a plan here, make a sandboxed version of the Lua standard library, figure out what to do with the Lua global environment, workout the GUI interface, where to store the scripts and I probably forgot some things and oh yeah this is why everything takes so long in these projects. It will be a very nice addition once it is working.

drwhut commented 1 year ago

The type of script I would ask to add is a utility/module script that on its own does nothing, but can be required/imported from other scripts (when you remove global functions for sandboxing, a custom require might be needed). These are wrapped in with the game room or asset pack or whatever fits with the rest of the design of Tabletop Club really well.

Yeah, by default I think the sandboxed API should be as minimal as possible for security - as an example, I don't think the File API should be automatically available, since that opens the door for potential malware. However, there will be creators that will need/want files as part of their scripts, so this could be a toggle in a configuration file, for example:

[Pack]

script_file_api = true

The idea being if this was set to true, then when the asset pack is first loaded it will ask the player for permission before allowing the scripts to access the relevant API. Then in the scripts themselves there would be require statements that would only work if the players gave permission.

Exported Variables! With honest to goodness code reuse having an easy way to configure things from the GUI editor (probably near were you go to set/edit the script) might be nice. Not really essential though.

That's a pretty good idea! That would go well with #73, and would avoid players having to dig into the Lua scripts to change certain values. Not immediately sure how this would be implemented, though. Maybe a set of functions that could be called in the global script?

But it sounds like there is a plan here, make a sandboxed version of the Lua standard library, figure out what to do with the Lua global environment, workout the GUI interface, where to store the scripts and I probably forgot some things and oh yeah this is why everything takes so long in these projects. It will be a very nice addition once it is working.

Yeah, pretty much :rofl: It would be really useful to have scripting for sure, but if not thought through properly this is probably entry point number one for bad actors to run code. Plus, if the design is better right from the outset, then this will save a lot of headache in the future with creators updating their scripts for newer versions of the game.

That's also part of the reason why I haven't assigned a milestone to this yet when I've set a lot of other features to come with v0.2.0, because the implementation is not 100% clear yet. A couple of examples of unanswered questions off the top of my head:

So given this, my plan as of now is to try and dedicate the v0.3.0 release to mostly scripting (since it's probably going to take a lot of time), and introduce a bunch of quality-of-life features before then in v0.2.0 so the API has more to work with.

Cluedrew commented 1 year ago

Life was distracting and this kind of got away from me. I'm going to start with the questions you ended with because they lead into the rest of what I'm going to say.

No, I don't think you should try to replicate Tabletop Simulator's API. Make an API best suited to Tabletop Club and don't worry about what Tabletop Simulator is doing. I also think there are advantages to sticking closer to plain Lua where possible. Tabletop Simulator made a lot of changes to its scripting environment from plain Lua for no reason I can discern. The only difference changing the math library to the Math library makes is now you can't copy in math code from other Lua projects.

Now, I'm not exactly sure what you mean by environment but I'm guessing you mean the global scope, where different modules can all see each other's writes (made to the global scope). I don't think there is a solid reason to go with a per-asset-pack scope or a per-module one. A truly global scope, stretching across asset packs with different maintainers, is probably a bad idea. I guess there is a soft reason to use per-asset-pack scope and that is it could be treated it is a per-module scope but entirely using local variables. I don't think the reverse is really reasonable.

The other thing that connects modules is requiring. I think you can have require without providing the file API. Say each asset pack has a scripts directory, and require only works on files in that directory, via some checks in a custom version of require. As long as the scripts themselves are secure through the rest of the API design, this shouldn't be a security issue at all. Under this model require takes the relative path from the scripts directory and objects and the save reach out to connect to their scripts, the script name is probably a configuration option.

And that is just running off requires within an asset pack. You could have version of require to get things from other asset packs but that seems like it would be both more complicated and not as useful, so maybe leave that until later.

OK, properties, well a lot of this depends on the details of the rest of the design of Tabletop Club, but the basic idea is you provide an object with a property name, a property type and a default value of that type and then the object adds that to some editing menu for the object.

self:add_property("Enemy Health", "number", 1)

I think that is everything. definitely take your time with it. Nothing hurts a good design like having to stick with backwards compatibility.

traverseda commented 1 year ago

No, I don't think you should try to replicate Tabletop Simulator's API. Make an API best suited to Tabletop Club and don't worry about what Tabletop Simulator is doing.

Theoretically an API translation layer should be possible as long as they both support the same functionality. You can see the tabletop simulator API documentation here: https://api.tabletopsimulator.com/

I agree that it's kind of crappy, and it's likely to be a moving target, so I don't think you should be targeting API compatibility. Honestly probably script compatibility is a much lower priority than asset compatibility, tabletop simulator has a ton of assets and it would be nice to be able to use those.

I'd like to be able to translate TTS's idea of a container to tabletop-club's idea of a container, as an example.

TTS has a big existing asset library which by it's very nature (a bundle of hyperlinks embedded in JSON) is not married to the steam workshop, you'd be remiss to not find some way to take advantage of that, even it it's just being able to load PNG files on to cards from the web.

drwhut commented 1 year ago

Yeah, I think I agree having Tabletop Club's scripting API being it's own thing is the way to go - even if theoretically a translation layer could be made, it would probably be a pain to develop because of the difference in design of the two games.

In terms of assets from Tabletop Simulator in general, this has been by far and away the most requested feature (for good reason), but I think this should be in it's own issue, otherwise we'd be going off topic here 🤔

jbromberg commented 8 months ago

Just wondering if any work has begun on this

drwhut commented 8 months ago

Not yet no, I'm currently in the process of refactoring the code for v0.2.0 - once that's done and I'm happy with the result, then I'll start work on the Lua API, most likely for v0.3.0 (although this is subject to change).