Everything in this repository is permissively licensed under the MIT
license, please refer to the LICENSE
file.
If you’d like to discuss ge_tts, you can do so on the TTS Community Discord.
For a quick overview of how to build a mod with ge_tts, please take a look at the demo project.
For development of your TTS mod we highly recommend using Jetbrains IntelliJ IDEA with a Luanalysis to write your code, instead of (or rather in conjunction with) Atom and the Atom TTS plugin.
IntelliJ Community Edition and Luanalysis are both free, and offer significantly more advanced Lua editing capabilities than Atom.
Most importantly, we’ve included Luanalysis type definitions for our APIs. This means that when you use our types, function and variable names will auto-complete. Additionally, when using Luanalysis you’ll want Tabletop Simulator Luanalysis Types which enable auto-completion and type checking.
However, at present there is not yet a Tabletop Simulator plugin available for Jetbrains IntelliJ IDEA. So you must still use Atom for loading code out of, and saving code into Tabletop Simulator.
ge_tts is split up into standard Lua modules. In order to use these modules you must require them.
ℹ️
|
e.g. local TableUtils = require('ge_tts.TableUtils')
log(TableUtils.merge({
a = 1
}, {
b = 2
})) |
The official Atom plugin has built-in support for require
. Otherwise,
if your IDE doesn’t support it, you can use
luabundler from command
line. However, this is an advanced solution, Atom is recommended for
pushing code to TTS.
To browse the precise APIs available, you should open up a module in your IDE and refer to the inline documentation (comments). What follows is a simply a quick overview of these modules.
Convenience functions for working with co-routines that are, for example, to be executed every X frames, or every X seconds.
e.g.
Coroutine.start(function()
print("Immediately")
Coroutine.yieldSeconds(1)
print("One second later")
Coroutine.yieldFrames(30)
print("30 frames later")
local object = CaspawnObject({type = "BlockRectangle"})
Coroutine.yieldCondition(function() return not object.spawning end)
print("After the object finished spawning")
end)
Simply utility to facilitate debugging within TTS.
A Zone
that acts as flexible, extensible and scriptable replacement
for TTS snap points. When an object is dropped in DropZone
the object
will be smoothly animated into the center of the zone (and rotated
accordingly).
DropZone
also have an optional occupantScale
which specifies how
dropped objects should be scaled (along the X-axis) when they’re dropped
in the DropZone, aspect ratio is always preserved. Automatic scaling can
be used to provide visual queues about important objects, or rather
objects placed in important locations/zones.
To extend DropZone
’s functionality you can `sub-class'' `DropZone
and override the filterObject
, onEnter
, onLeave
, onDrop
and
onPickup
functions as desired.
DropZone
is itself a sub-class of Zone
, so for an example of how you
can extend a `class'' please refer to `DropZone.ttslua
(or
HandZone.ttslua
).
TTS has several events which are called as global functions on a script. It’s fairly common to have several objects or unrelated pieces of code that are interested in these events.
EventManager
allows several pieces of code to subscribe to the one
event. If you have already written global event handler functions you
must move their definition above any require()
of ge_tts modules in
the same script, otherwise your exising handlers will interfere with
EventManager
.
A package with functions useful for working with node hierarchies e.g. TTS UI (``XML'') tables.
A Zone
that belongs to a player (owner) and corresponds with one of
their hands (most games just have the one hand). When instantiated
HandZone
will automatically size itself to encompass the associated
TTS hand zone so that you can programatically track cards that are in
the players hand.
Typically, to make use of this package you’d create your own
package/`class'' where you extend `HandZone
and override the
onEnter
, onLeave
, onDrop
and onPickup
functions as desired.
HandZone
is itself a sub-class of Zone
, so for an example of how you
can extend a `class'' please refer to `HandZone.ttslua
(or
DropZone.ttslua
).
Http convenience module which wraps Tabletop Simulators WebRequest API. The Http module will automatically encode/decode JSON, otherwise you can provide a string and specify headers yourself.
❗
|
ge_tts does not presently support Instance being stored
in nested containers i.e. Cards placed in a deck are fine. However,
ge_tts is presently unable to track Instance referring to a card in
a deck in a bag.
|
Please refer to ge_tts_demo for a demonstration.
Unlike TTS objects, which are destroyed when entering a container, instances more closely resemble the concept of a real world game piece, and are only destroyed if you delete the object in TTS.
Instance
also provides some convenience methods that help you interact
with TTS objects. For example, reject()
knows how to return a TTS
object to wherever it previously came from; either its previous zone, or
if it has never been in a zone before, wherever it was picked up from.
|
This is an advanced feature, and makes implementing saving and loading more difficult. |
InstanceManager
exists for the sole purpose of improving save
performance.
InstanceManager
is beneficial if your mod has a lot of Instance
(typically 500+) or some of your Instance
sub-classes are storing a
lot of data that changes infrequently. InstanceManager
essentially
introduces a caching layer, that results in each instance’s save()
being called only when absolutely necessary, and most importantly,
smaller less frequent JSON encodes.
-
You enable use of an
InstanceManager
withInstanceManager.set(yourInstanceManager)
.💡You don’t need to sub-class
InstanceManager
.InstanceManager.set(InstanceManager())
is perfectly acceptable.
-
Your main module’s
onSave
(SaveManager.registerOnSave
) must callInstanceManager.save()
andonLoad
(SaveManager.registerOnLoad
) must callInstanceManager.load()
. -
You must call
self.invalidateSavedState()
on anInstance
, if you know its saved state is dirty. -
When saving an instance, call
InstanceManager.saveInstanceState(instance)
and store the returned instance GUID only. As opposed to callinginstanced.save()
and storing the generated saved stated (which is what you’d do without theInstanceManager
). -
When loading/recreating an instance, call
InstanceManager.loadInstanceState(instanceGuid)
to obtain the saved state of theInstance
, which you’ll then provide to theInstance
’s constructor.
When enabled InstanceManager
will persist Instance
saved state
(i.e. return value of save()
) to the corresponding TTS object’s
script_state
.
A DropZone
that is associated with a particular TTS player,
specifically instances have an additional getOwner()
.
A Logger
that rather than printing to TTS’ console, will HTTP PUT
a
JSON object with messages
(array of strings) to a URL that you provide
when instantiating the RemoteLogger
.
Using HTTP PUT
instead of POST
is pretty severe abuse of HTTP
semantics, however we don’t have a choice as TTS’ HTTP functionality is
severely lacking and cannot POST
JSON.
|
The Content-Type of the request is octet-stream instead
of the correct type application/json . As mentioned, TTS’ HTTP client
is currently very limited and does not allow us to set headers.
|
We don’t presently provide a corresponding server, but it’s pretty trivial to create your own in Python, Ruby, Node.js etc.
Remote logs could be useful for diagnosing issues your players are running into, however personally I just use it in development as my logs are kept even if TTS crashes, and it’s easy to copy and paste data from my logs etc.
SaveManager allows modules/files to independently maintain their own saved state, without conflicting with other saved state from other modules/files.
Several convenience methods to be used in conjunction with tables.
|
For both performance and semantic reasons, this module will only operate on tables that are either arrays or hashes/maps, but not tables that are both simultaneously. Behavior is undefined for tables that contain a key for [1] as well as non-consecutive integer, or non-integer, keys. |
3D vector implementation.
This was written before TTS had its own Vector
class and is used
through-out this library. You may pass Vector3
to any TTS method that
accepts a vector. However, it’s worth keeping in mind that our methods
return a Vector3
, whilst TTS’s own methods return Vector
.
In general TTS’ Vector
and our Vector3
offer a similar set of
functionality, however you can call Vector3
methods the same way you’d
call methods on any complex type in TTS API i.e. vector1.add(vector2)
,
where as TTS’ Vector
requres you to do vector1:add(vector2)
.
Additionally, all Vector3
methods will happily accept a Vector3
, a
Vector
, a table with entries x
, y
and z
, or a table with entries
[1]
, [2]
and [3]
as arguments. Where as the TTS-provided Vector
is a bit more restrictive and will only accept arguments that are also
Vector
e.g.
local v = Vector()
v:scale({1, 3, 1}) -- This will throw an error
local v3 = Vector3()
v3.scale({1, 3, 1}) -- This works fine, as does...
v3.scale(v)
v3.scale({x = 1, y = 3, z = 1})
A wrapper around a TTS scripting trigger (ScriptingTrigger
) that
tracks dropped and picked up objects. Objects that have been dropped in
the Zone
are deemed to be occupying and can be retrieved with
getOccupyingObjects()
.
Typically, you’ll want to use a DropZone
, PlayerDropZone
or
HandZone
rather than Zone
. However, you may sub-class Zone
if you
wish.