Thursday, 23 November 2017

Debugging your TTS mods with console++ : Batching and Aliasing

This continues the series which started here.

The first thing we'll do in this session (after loading the example mod in TTS and loading it into Atom) is use console++'s autoexec feature: this allows you to set a series of commands which will be executed when your mod is loaded.  In order for this feature to work you need to call console.load() in your onLoad function; the example mod already does this as the last line:


With that line present all we need to do to make console++ execute a script on startup is set it in the console.autoexec variable.  Add this block of code above the onLoad function:



console.autoexec = [[
    cmd
    cd /console
]]



Hit Save And Play; now when we hit enter in TTS and type a command we don't need to prefix it with '>' and don't need to remember to switch to command mode - the 'cmd' command has already done that for us.  Type 'ls' to test this out:


You can see we are already in command mode (the command worked without a '>'), and are indeed inside the console table.

Now, the autoexec feature isn't groundbreaking: it's not doing anything we couldn't simply do with Lua commands.  Instead of making an autoexec script we could have written this code:


This accomplishes the same thing: sets you to command mode and changes your current location.  It was simpler to just type the console++ commands though, no?  Setting up the autoexec has an additional benefit; we can invoke it manually using the 'exec' command.  Type 'cd /' to return to the root table, then 'exec console/autoexec', then 'ls': you'll see we are back in the console table.  

Thus you can set up batches of instruction in strings in your program, and then run them with the 'exec' command; console.autoexec isn't special in this (it's only different because it is run automatically on loading) - any string variable can be executed.  

As with any command which takes a variable as an argument, we can make it work on a literal string too: thus we can use 'exec' to run a sequence of commands entered as one line.  To do this we need to know the use of these three punctuation marks in console++:
  • A string parameter prefixed with a back-tick (`) is treated as a literal (instead of a path)
  • Double- and single-quotes (" and ') can be used to surround a parameter containing spaces in order to stop the spaces delimiting the parameter.
  • The semi-colon (;) can be used to separate commands in a script instead of a new line.
So to duplicate the above autoexec script on a single line we type this:

exec "`cmd; cd /console"

The command line sees the surrounding double-quotes and treats everything inside them as one parameter. It then sees that that parameter starts with a back-tick and so knows it is a string literal, and then the 'exec' command splits that string up by its semi-colons.



We'll come back to 'exec' later; right now we're going to look at the 'alias' command.  'alias' allows you to create your own commands from any existing commands.  At its simplest this simply lets you rename commands to suit yourself: console++'s 'ls' and 'dir' commands are actually the same command: one is simply an alias of the other.  If you wanted to add your own name you can use 'alias' to do so.  Let's say you are especially verbose, and you want to type 'list' instead of 'ls': simply enter 'alias list ls'.

This is a pretty silly example; lets make it actually useful.  By default the 'ls' command only displays variables and tables - it does not display game objects or functions. To display everything in your current location you use the '-a' option: 'ls -a'.  Let's set up an alias for that: at the command prompt type 'cd /' to go to the root then 'alias list ls -a', and then 'list':


I typed 'ls' afterwards to compare the results: you can see our 'list' command displayed the functions as well as the rest, while 'ls' did not.  If you like this behaviour you can include this alias command in your autoexec to make it available whenever you run your mod.  You could even take it a step further, and alias the 'ls' command over the top of itself with 'alias ls ls -a', which would replace the default ls behaviour, allowing you can just type 'ls' to see everything (though a better alias for this would be 'alias ls ls -fov', as this will allow you to toggle them off again; '-a' overrides any other parameters)

You can check what a command does with the 'help' or '?' command (as with ls/dir, one of these is just an alias of the other). Type '? list' and you'll see:




Now we're going to combine these two commands, 'exec' and 'alias', to create some commands for controlling our mod.  Enter these commands:

alias slow exec -q "`set /update_cards true; set /check_delay 2.0"
alias fast exec -q "`set /update_cards true; set /check_delay 0.2"
alias off set /update_cards false

After entering these aliases we will have three new commands: 'slow', 'fast' and 'off'.  Typing 'slow' or 'fast' will enable the card display updating to match the dice total; one being more responsive than the other.  Typing 'off' will disable the card updating.  Try them out!

The '-q' (quiet) option tells the 'exec' command not to display the result of each command it is executing; just to display the final result.  If we used the '-s' (silent) option instead then the final result would also be suppressed.  Alternatively we could have used the '-v' (verbose) option to make 'exec' display all the commands it is executing as well as their results.  Note we don't need a back-tick before true and false - these are special cases.  If we were assigning a string we would need the back-tick, or it would try to look up the string as if it were a path.

These are nice, but no good if you have to type them in every time you open your mod in TTS.  That's OK though; we can add them to our autoexec!  Remove the 'cd /console' command and add them in like this:


Now whenever you load your mod you will start in command mode, and have these commands at your disposal.

Monday, 18 September 2017

Debugging your TTS mods with console++ : Functions

Don't just jump in here: start at the beginning!  Once you've loaded the module in Tabletop Simulator remember to switch to the Game tab and turn on command mode with '>'.

In the previous posts we've seen how we can display variables with 'ls' (or 'ls -v') and objects with 'ls -o'; the final option available is to display functions with 'ls -f'.


Here you can see the functions defined in the example mod.  We can do more than just display them, however; we can also call them.  Type 'call dice_total', then roll the dice and call it again.


What if we want to store the value a function outputs?  console++ defines a special path for the last returned value: the '~' character.  You can see this if you display it with 'ls ~', and all other commands can use it like they would any other path; this includes 'add' and 'set'.  Add a variable called total using the '~' path and check it's set to the right value:


The 'add' and 'set' commands can also be used to store a result directly, by calling them with no value argument before the 'call' command.  If you do so the the next 'call' command will store its result where you have specified.  Roll the dice again and then try it by typing 'set total' and then 'call dice_total':



Note that console++ also has a short-cut command to display the last result: simply type the result character on its own to display its contents: '~'


We can pass parameters to functions just as you would expect, by adding them to the 'call' command.  Let's use the is_face_up function on one of the cards.  Remembers, the card objects are stores in the cards table; you can see them by typing 'ls -o cards'.
Type 'call is_face_up cards/2' to call is_face_up on the 2 card, then flip the card and issue the command again (use up-arrow to get the last command typed instead of typing it again).



Note how when we 'ls -f' we see only the functions defined in the mod; we don't see all the built-in Lua functions.  That's not because they are not present; it's because console++ hides them under the 'sys' label.  If you do an 'ls' you will see two things at the bottom of the tables that look like tables, except they are in a salmon colour instead of yellow; these are labels used to hide globals to keep things tidy.  You can see what they are hiding by calling 'ls' on them;
try it now: 'ls sys -f'


At the point in the Lua code where you #include console++ it automatically scans all the globals and hides them under the `sys` label, thus keeping your mod easy to interact with in the console.  All these things still exist in the root table, though they may also be accessed via the label they are hidden under. i.e. 'ls /math' and 'ls /sys/math' are the same thing:



You can also create your own labels to hide things under by calling console.hide_globals in the mod's Lua code.  Type 'ls' and you'll see that as well as the 'sys' label there's also a 'guids' label; if you look at the end of the GUIDS block in the Lua code you'll see the command that created it; whenever you call console.hide_globals it will scan for global variables/functions etc and hide them under the label you specify (provided they haven't already been hidden under another label).



To come back to the point: all the built-in functions are available should you wish to call them.  For example, we can get a color using stringColorToRGB then display the RGB values:
'call stringColorToRGB Blue' then '~':



We can then store it in a variable and use it to highlight an object:
'add blue ~' then 'call /decks/b/highlightOn blue 100'


Thursday, 7 September 2017

Debugging your TTS mods with console++ : Manipulating Variables

If you haven't read it then you should check out the first installment of this series, which goes over how to get set up.

Last time we saw how to look at our program's structure.  This time we'll see how we can manipulate it in real-time by changing and adding variables.

First things first though; last time we were entering commands by typing the command symbol ('>') and then the command.  Let's shortcut past that by entering command mode.  You can type '>cmd' at the prompt to enter command mode, and when in command mode you can type 'exit' to leave it.  When you are in command mode the console will interpret all your inputs as commands, so you no longer need to prefix everything with '>'.
But we're not going to type those!  Instead, an even easier way to enter and exit command mode is to just type the command character on it's own; if you hit '>' then enter it will toggle you in and out of command mode.  Try it now:


After I entered command mode I typed 'cd cards' (just like last time), and you can see that while in command mode any commands you perform will end with a prompt which includes our current path.

OK, so now we can simply type commands without a prefix, let's try a new one out: the 'tgl' (or 'toggle') command flips a boolean.  Let's go back to the root table with 'cd /' then remind ourselves what is there with 'ls'.


At the bottom you can see the four global variables in the mod, and the bottom one is a boolean, currently set to false.  If we look at the code we can see what it does:


update_cards acts as a sentinel on the onUpdate event. Let's turn it on so the mod actually does what it's supposed to do.  Type: 'tgl update_cards' and see what happens.


Well, that isn't good.  The mod clearly doesn't work right, and worse, because the bug is in the onUpdate event it is spamming errors every time it engages.  Investigating will be impossible while the console is being filled with error messages, so turn it off again: in the console hit up-arrow to get the previous command and enter to execute it; this will toggle the update_cards boolean back off, disabling the faulty onUpdate event code.  Note this technique; it's always a good idea to wrap timer-event driven code inside a boolean like this so that if things go wrong you can easily disable the code without aborting from the faulty session.

So what's the problem?  The error is on line 1542, so go to the global script in Atom and hit ctrl-g and enter 1542 to go to the offending line number.  The Atom plugin will take into account any #includes in the code, and will take you to the correct line, which in this case is the one that reads 'cards[count].flip()'.

The error is suggesting that we're trying to index a nil value, so either cards is nil, or the individual location of cards[count] is nil.  Let's have a look: type 'ls cards' at the console.
Nothing? Oh, the cards are game objects; try 'ls -o cards'.


There they are; cards is clearly not nil.  Let's look again at the code - what is the cards table supposed to do?  You can see in the onUpdate event it is looping over the numbers 2 to 10 - the card values on the table.  The table has been set up such that to access the 2 of Spades we use cards[2], to get the 3 of Diamonds we use cards[3], etc.  In the onUpdate event we are looping over each of the cards from 2 to 10 and possibly flipping them.  However, look at the 'ls' output: the cards are not indexed from 2 to 10, they are indexed from 1 to 9.  Looks like the table has been set up wrong! There is no cards[10] so when it tries to access it on the last iteration of the loop it doesn't find anything, thus the error message.
Before we jump back into the code to try to track this down, let's confirm that that is the problem; we'll add a card at that index and see if the code works.  To add a variable we use the 'add' command.  We'll set it to the card that's not being looped over: cards[1].
Type 'add cards/10 cards/1', and then 'ls cards' again.


You can see it has a card at index 10, and it has the same GUID as the one in index 1.  Turn on the code by toggling the boolean: 'tgl update_cards'. No errors - nice!  The update code is working, but our table is still set up wrong; all the indexes are off by one (as you can tell by the card 3 being face up, but the total of the dice only being 2).  Select the dice and hit 'r' to roll them.  The cards will update, but always to the wrong value.

We now know what to fix in the code though, and on investigation you can see the part that sets up the table:



This looks pretty straight-forward; the table is created with a null value in index 1, so that when it loops over the GUIDs of the cards it will add them to slot 2, 3, 4... etc. all the way up to 10.  However, if you are more familiar with Lua than the programmer of this code you will know that nil does not behave just like null does in other languages; in Lua when you assign nil you are actually deleting that entry, so this code is no different from typing 'cards = {}'... it makes an empty table.  The fix should be simple: change the line to 'cards = {'dummy'}' and hit Save And Play.  Now we can go back into command mode with '>' and type 'ls -o cards' to see that they are correctly indexed, 2-10.  (An exercise to the reader: where is the 'dummy' card?)

Toggle the update_cards variable to turn on the event code.  No errors, and the card being displayed matches the total of the dice!  Select the dice and hit 'r' to roll them, and you can watch the cards flip to match.

It feels a bit clunky though; the cards are updating very slowly.  Type 'ls' again to see what variables we have to play with: sure enough, there's a 'check_delay' number set to 1.5 seconds.  Let's set it to a shorter delay using the 'set' command.  Type 'set check_delay 0.3' then select the dice and roll them.  Much more responsive!  If you were writing this code you could easily play with varying values for check_delay until you found the one you liked the best, and then amend the code afterwards to match.

That covers the basics of manipulating variables in the run-time environment.  You can see how we can use it to debug our code in a controlled manner, and also to help in refining control variables. We've now looked at simple variables and objects; next time we'll look at the third of the triad: functions.

Monday, 4 September 2017

Debugging your TTS mods with console++ : Introduction


The typical cycle for debugging a game in Tabletop Simulator is to play it, wait for an error message to emerge, use that error message to find the problem, write a potential fix, then start again.  This is less than ideal in a lot of ways: the error message can be a good clue for what the problem is, but sometimes it can be extremely vague (sometimes it doesn't even have a line number!).  Potentially more time-consuming is that when your game is getting fairly well developed the bugs become hidden further in; it may take a lot of setup to get to the specific circumstances that trigger the bug.  Typically this means that once you think you've written a fix you then need to play the game out exactly the same was as before, coordinating any playtesters to do what they did before, until the bug manifests again... or does not, and is potentially fixed - but how do you know for sure?

console++ gives you live access to your mod's structure, treating it as if it were a file system.  It lets you examine your variables, functions and objects, and also lets you update them, all without leaving the game.

To follow along to this tutorial you need to get console++ from the link above and subscribe to the example mod on the Steam Workshop.  Once you've loaded it into Tabletop Simulator, save it to your own hard disk so you can save your edits.

Source code is visible here.
This post will cover navigating and displaying your program using the two basic commands in console++: 'ls' and 'cd'.  As a quick aside, I'll be using the unix-like command names during this tutorial ('ls'), but console++ includes DOS-like alternatives, so if you prefer DOS you can use those instead.  In this case, anywhere I say 'ls' you can type 'dir' instead.

What does 'ls' do?  Well, we could ask for help on it, but lets do the impetuous thing and just type it into the console; make sure you are on the Game tab of the console, not the Global tab it defaults to (also it would be a good idea to hit the cog and disable autohide if you have it turned on).

So, hit enter, type 'ls' and hit enter again.  Congrats, you just said 'ls'; the console is just the chat window after all, and you didn't tell it you wanted to use a command!  To use a command you type the prefix '>' before it, so lets try again, typing '>ls'.  Much more interesting, and if you looked at the source file above it should look a little familiar:


You can see the variables we defined in the source are there; the 'ls' command lists all the tables and variables in your current location.  Right now we're in the root of our program ('/') which is the global level.  You can see at the top of the listing some labels in yellow: these are tables.  Let's see what's in them; type '>ls decks'. Nothing there?  That's because by default the 'ls' command only displays tables and basic variables (numbers, strings and booleans); to get it to show game objects we need to use the '-o' option.  Try '>ls decks -o' and you'll see:


There are our deck objects: table entries a, b, and c.  We can see their GUIDs listed next to them; right click a deck on the table and check the Scripting submenu to see it matches up.

We can refer to each of these decks by their path: if you type '>ls decks/a' you'll get the listing for only deck a.  Now, if we were debugging something to do with these decks, and were going to type their names a lot it would be tedious to have to keep specifying their location.  Instead we can change our current location to suit our needs, with the 'cd' command.  Type '>cd decks' and then '>ls -o'.  As you can see we have changed the table we are in; we can now refer to the decks simply by their letters: '>ls a'.  Note that when we specify an exact path we do not need the '-o' option.

Paths behave just as you would expect for a file-system; you can specify relative paths from your current location or absolute paths from the root table (by starting your path with a '/') You can refer to a parent table by using '..' and your current table with '.'

Let's go back to the root table: type either 'cd ..' to go up one level (we are only one level below the root) or 'cd /'.  This example mod is clearly very small and simple, but you can imagine in a big game you will have lots of variables and tables; simply typing '>ls' and scrolling up and down looking for things could be cumbersome.  In that situation it would be good to use a wildcard to trim down what you're looking at: try '>ls c*'


That's the basics of navigating and examining your program from within TTS with console++, except to return to what was said at the top of the post about asking for help: you can type '>help' or more simply '>?' to get help on any command, and the commands with more complicated options will also support a '-?' option to provide more detailed information.  If you type '>? ls' you'll see it has a few options we haven't tried yet.  To wrap up lets try '>ls -ar' ...



Next time we'll look at actually interacting with your program by modifying variables in real time.

Thursday, 24 August 2017

Atom Tabletop Simulator package, experimental #include system

A note about the experimental additions, specifically the #include:

You can #include other files into your source; when you hit Save And Play the plugin will go and grab those files and inline them in your code at the point you have placed them. The files are looked for inside the folder specified, or you can specify a complete path to their location.

For example, on the game I'm working my Global.-1.ttslua is now simply:

-- workshop id = 945458382

#include Shard/shard
When I hit Save And Play it will look for the shard file in the folder location specified in the settings: by default this is your USER_FOLDER/Documents/Tabletop Simulator. So in my case it will get the file:

C:\Users\onelivesleft\Documents\Tabletop Simulator\Shard\shard.ttslua
- Shard\shard.ttslua being specified by the #include 

My shard.ttslua file looks like this:
Code:
#include constants
#include guids
#include globals
#include levels
#include !/utils

#include admin
#include game
#include draft
#include rewards
#include dice
#include expansions

--NO_SAVE_OR_LOAD = true  -- To remove any state for clean upload to workshop
--DEBUG_DISPLAY   = true

game_started = false
loaded = false

function onload(saved_data)
    current_level = nil
    ...
When the plugin fetches it, it will then look through it inlining the includes it has, and so on. This time it won't look in the folder specified in the settings, it will look in the same folder as the file which included them. My file structure looks like this (and is a git repo!):



Almost all the files included by shard.ttslua are in the same folder as it, so I only need their name.

Note the line: #include !/utils - the !/ is an identifier that specifies the folder specified in the package settings.  In this way I can have a utils library sitting in the root of my TTS folders, accessible by all my games.  You may have a vector library you wish to include in all your objects' code; now, this is easy to do.  You may also use the symbol ~/ to start a path from your user home folder (even on Windows), or specify a full path on the #include line if you want to.

In practice how this works is it takes all the text in the files you are including and dumps them into the source at the point they are included.  This means if you have a bunch of functions you want your objects to have access to then you can, but they are in effect being repeated in every object. 

As it stands this means all your modules are sharing the same namespace; a global in one is accessible in all.  I'm OK with this (it's the Lua spirit), but if you want to make them behave like modules you can, you just have to do it explicitly.  

  1. Start the module by declaring itself as a table.
  2. Prefix all it's "globals" and function with that table.

Let's say you have a module to greet the players, saved as greetings.ttslua :

greetings = {}

greetings.default_greeting = "Hello!"

function greetings.greet(message)
    local message = message or greetings.default_greetings
    for p, player in pairs(getSeatedPlayers()) do
        printToColor(message, player, rgb)
    end
end

Then you can use it in your program by:

#include greetings

greetings.greet()
greetings.greet("Hi!")
greetings.default_greeting = "Yo!"
greetings.greet()

When you get the script from TTS the atom plugin will automatically strip out the inlined code and give you what you expect.  Note that the package commands like Go To Function will take into account all your includes, and when you use them will jump to the correct place.  If you are in your Global.-1.ttslua file and type a line number into the Go To Function dialog it is smart enough to know which line you are referring to and take you to it.  i.e. even though the program I listed at the top of this post only has 3 lines when I see it in the editor, when TTS says there's a bug on line 4345 I can hit ctrl-g and type 4345, and it will take me to the correct line (opening up the relevant included file if necessary).

Finally, note that when the file is loaded into atom from TTS the undo buffer will record the changes made by this system, so if you want to see the raw code that TTS is seeing you merely have to hit ctrl-z a couple of times after loading.

Thursday, 6 April 2017

Shard changes after 01/04/17


  • Energy Shield
    Passive and active now grant +2 TN against ranged (instead of +1 soak dice)

  • Wrist Blaster
    Now a reaction, can be activated on any coach's turn (ranged 1 dam 4)

  • Pulse Pistol
    Increased attack roll to AGI+1 (up from AGI)

  • Pulse SMG
    Increased attack roll to AGI+1 (up from AGI)

  • Pulse Cannon
    Increased attack roll to AGI+2 (up from AGI+1)

  • Launcher
    Increased attack roll to AGI+2 (up from AGI+1)

  • Moment of Truth
    Buffed built in actions (was just a [move]). Now reads:
    If you only have one player who is not KO'd or incapacitated then this card may be played on them (even if this is not their card). They may take any or all of the actions listed below (instead of choosing just one of them). // [[move] or [attack] or [use]]
  • Passing Play
  • Buffed ability text to:
    If this player passes the Shard this turn roll [dice:+1] for the pass. If the receiver catches the Shard and has not been activated this turn then when you activate them you may have them take a [move] instead of playing a card.
  • Coruin Hlest - Steal
    Fixed so he can steal from an adjacent player without having to move first.

  • Shadow Dancer - The Weaver
    Nerfed: removed riposte ability of parry.

  • From Dawning Minutiae
    Buffed their passive (before it only triggered if they were discarding to stand up):
    When a coach wants to play a card belonging to a player who is adjacent to From Dawning Minutiae they must first discard another matching card from their hand.
  • Gaffer Jones
    Changed his ability for attaching cards to Automata (added turn restriction, but allowed any Basic card):
    [react] : During Gaffer Jones' activation, once per turn, attach a card from your hand to an Automaton (discarding any card already there). The card must match Gaffer Jones or be Basic.
  • Gaffer Jones - Automaton
    Gave Automata an in-built Use ability:
    [use] : Move [mp] then [melee]
  • The King
    Removed his passive that made removing an injury not cost a star.  Instead gave him:
    When flipping a card for this player from the Critical Strike deck flip an extra card and choose which one they get.  Use this ability after any other abilities which deal with cards flipped from the Critical Strike deck.
  • Pickles - Deploy
    Gave Pickles' Emblem card (which was a standard Deploy: [use]+[move]) the Team trait.

  • Switchback - Energy Shackle
    Fixed wording, using written "attack" and "move" instead of icons.  Now reads:
    [use] : Make a [target:2] on an enemy player.  If you hit then they are held immobile, unable to move or attack, for as long as Switchback stays upright in the same square or until the start of your next turn, whichever is sooner.

Clarified the jargon on "teammate"  and "ally": an ally is another unit in your team, while a teammate is an ally who is also a player.  Used the correct one of these terms on these players:
  • Oubliette - Barricade
  • Webspinner
  • With Unfailing Anticipation - Recall

Sunday, 26 March 2017

Shard changes after 25/03/17

Blister

Removed the range restriction on her emblem card ability.  It now reads:
Choose an enemy player who Blister can see who is not in cover and has an empty adjacent square under the LOS. Warp Blister to that square then [melee] them. 

Havok

Changed his emblem card ability to:
Place the Suppression marker in any square.  Whenever an enemy enters a square within 2 squares of it Havok may make a [ranged] against them if able. Havok gains [dice:+1] when making a ranged attack against anyone within 2 squares of the Suppression marker.  Remove it from the board at the end of your next turn.  
Place the Suppression marker in any square; if Havok is Downed remove it. Whenever an enemy enters a square within 2 squares of it Havok makes a [ranged] against them if able.
If it is still on the board at the start of your next turn then Havok makes a [ranged] against one enemy within 2 squares of it, then remove it. 
Place the Suppression marker in any square; if Havok is Downed remove it.  The first time in a turn an enemy enters a square within 2 squares of it Havok makes a [ranged] against them if able.
If it is still on the board at the start of your next turn then Havok may make a [ranged] against one enemy within 2 squares of it, then remove it regardless.
A player may only take one [attack] action per turn. 
 


Rules

 Adding this rule:
Whenever you make any roll for a player carrying the Shard they will fumble it if you roll as many [crit] as their Agility; scatter it from their square.  When they attempt to pass the Shard the roll is first checked against the thrower then the receiver (scattering from the relevant square if applicable)
"Fumble TN" is no longer a thing: adjacent enemy players no longer affect this mechanic.

 Considering adding this rule:
Whenever the Shard carrier makes any roll (other than to contain the Shard), if they roll as many [crit] as their Agility then the Shard will surge.