Starting with Toribash 5.4, I added new way to create and run tutorials which doesn't require extensive lua knowledge.
This guide expands on how to create your own tutorials, what they can be used for and how to run them afterwards.
Please make sure you're using the latest available version of Toribash before you start - tutorials backend keeps receiving updates with every new version, and while 99% of what you'll see below will likely work correctly in 5.4, it's better if you have the newest version.
New tutorial system basics
New system allows (relatively) simple creation of new tutorials / events which may feature replay cutscenes, load various mods, display messages and so on. You can see it in action by loading any tutorial or ingame event (e.g. Hole in the Wall) in modern UI.
Any tutorial consists of minimum 2 data files, one of which contains tutorial instructions and the other one is a localization file for messages. Instructions are then loaded by tutorials manager class and run as an actual tutorial. This means that for simpler tutorials you won't need any lua coding knowledge.
Tutorials consist of steps and use a
specific set of instructions which are similar to Toribash chat commands or Toribash-specific lua functions. All instructions can be roughly divided into two groups: those that act as conditions to pass to next step and those which don't (this is also expanded upon below). Once all requirements to end the step are met, current step is finished and the next one starts.
Creating your first tutorial
Let's say we want to make a tutorial that plays 150 frames of a replay, then shows a message and then asks the player to move one of Tori's bodyparts.
To do that, we first create two data files which will be used for the tutorial. Say the tutorial is going to be called
mytutorial1 , this means we make two files named the following way:
- mytutorial1.dat - this file will contain all tutorial instructions.
- mytutorial1_english.txt - this file will contain all tutorial messages in English
One thing to keep in mind - your tutorial
must have an English localization. You can add other localization files (and may define all the messages there), but [i]tutorialname[i]_english.txt file has to exist.
After these files are created, open .dat file with any text editor.
With the first step, we want to load a replay. We'll also hide hud and make sure current player's character is displayed on Tori spot:
STEP;
OPT hud 0;
LOADREPLAY mytutorial_replay.rpl;
LOADPLAYER 0 PLAYER;
Every step has to begin with "STEP;" instruction. After that, we use OPT instruction to set hud to 0, load our replay and load PLAYER (current user) on spot 0 (Tori). Note that LOADREPLAY doesn't instantly play the replay - it's getting paused instantly after loading.
For second step, we play 150 frames of the replay:
STEP;
PLAYFRAMES 150;
This one is pretty obvious,
PLAYFRAMES 150; instruction plays 150 frames and then pauses the replay.
Third step we display our message. There are two ways of showing messages to user: we can show a general "hint" message or make it look like that's some tutorial character speaking. For this one, we'll go with a character line:
STEP;
SHOWSAYMESSAGE;
SAY mtCharacter;
MESSAGE INTROMSG;
WAITBUTTON;
SHOWSAYMESSAGE; command triggers message box. You only need to run this before showing your first message to user - if we wanted to display another message instantly after this one, having this command among step instructions won't do anything.
SAY [characterName]; commands sets message author to display their name and head texture (if found in customs folder).
Here's an example of how it looks.
After that, we set the message that's going to be shown to the user:
MESSAGE [messageStringName]; . Message string name should correspond to one of text strings from tutorial localization file - see more below.
Last command we add is
WAITBUTTON; which pauses tutorial execution until user presses enter or continue button. This is optional, but giving user some time to make sure they've read the message is usually the way to go.
After the message is shown, we need to hide message box and enter edit mode to later wait for user to move a bodypart on their character's body:
STEP;
HIDESAYMESSAGE;
STEP;
EDITGAME;
SHOWTOOLTIP;
MOVEJOINT R_KNEE FORWARD;
With the first step, we hide say message box, simple.
After that, we enter edit game mode (as we previously were in replay mode), show tooltip (optional) and set joint movement requirement with
MOVEJOINT [jointName] [jointState]; command. This one specifically, would require the user to change their right knee's joint state to "Extending".
That's it, now we're finished with the .dat file. The only thing that remains now is to set the intro message in localization file. To do that, we put the following line in mytutorial_english.txt file:
INTROMSG Hi, this is my first tutorial!
Pay attention to the fact that string name and the message have to be separated by tab and not just any whitespace character.
And now the tutorial is ready to be run! You can read on how to run it
below.
Data file syntax
I've explained some of instructions data file commands above, this is a complete list of all currently available (as of Toribash 5.42) commands:
Notice: all commands must be followed by semicolon (;)
Command arguments preceded with ? are optional.
Commands marked with
* act as conditions to pass to next step.
Commands marked with
* are optional conditions and require a proper condition to pass to next step to work correctly.
- STEP
Starts a block of step instructions.
- STEPSKIP [n]
Defines how many steps should be skipped after finishing current step.
Example usage: STEPSKIP 1; command for first step will make it skip second step after completion and go straight to third one.
- STEPFALLBACK [n]
Defines how many steps to go behind after finishing current step.
Example usage: STEPFALLBACK 1; command for second step will load first step after completion.
- *NEWGAME [modname.tbm]
Starts new game in a defined mod.
Example usage: NEWGAME aikido.tbm; to start a new aikido.tbm match.
- *LOADREPLAY [replayname.rpl] ?[cached]
Loads and pauses a replay from replays/system/tutorial/ folder.
To load replay with cache (replay speed instantly available), set 1 as cached value.
Example usage: LOADREPLAY myreplay.rpl 1; will load a replays/system/tutorial/myreplay.rpl replay with cache.
- ENABLECAMERA
Enables user camera controls.
Camera controls are also always force-enabled once the tutorial is finished.
- DISABLECAMERA
Disables user camera controls.
- *DAMAGE [n]
Sets a damage requirement to proceed to next tutorial step.
Example usage: DAMAGE 50000; will require user to gain 50K damage to proceed to next step.
- *DAMAGEOPT [n]
Sets an optional damage requirement. If requirements are met, sets step skip to 1.
DAMAGEOPT is used in punching tutorial's final task when player has to get 500k points to get a prize.
This command has to be used along with regular condition to pass to next step.
- *DISMEMBER [jointname]
Sets a Uke dismember requirement. Joint names must be uppercase.
Example usage: DISMEMBER R_KNEE; will require user to dismember Uke's right knee.
- *FRACTURE [jointname]
Sets a Uke fracture requirement. Joint names must be uppercase.
Example usage: FRACTURE NECK; will require user to fracture Uke's neck.
- *SHOWSAYMESSAGE
Slides character message box from the right side of the screen.
- *HIDESAYMESSAGE
Hides character message box.
- *SHOWHINTMESSAGE
Shows hint message box.
- *HIDEHINTMESSAGE
Hides hint message box.
- *SHOWTASKMESSAGE
Shows tasks box.
- *HIDETASKMESSAGE
Hides tasks box.
- SHOWTOOLTIP
Enables advanced tooltip (doesn't affect user's settings).
- HIDETOOLTIP
Hides advanced tooltip (doesn't affect user's settings).
- SHOWWAITBUTTON
Enables "press to continue" button (if disabled).
- HIDEWAITBUTTON
Hides "press to continue" button.
- *TASK [taskname]
Adds a task with checkbox to tasks box. Task name should correspond to a string defined in localization file.
Example usage ►Instructions .dat file:
[...]
TASK MYTASK;
[...]
Localization .txt file:
MYTASK Fracture Uke's neck
- TASKCOMPLETE
Marks current main task as completed.
- TASKOPT [id] [opttaskname]
Adds an optional task with checkbox to tasks box. Task name should correspond to a string defined in localization file.
Example usage ►Instructions .dat file:
[...]
TASKOPT 0 MYOPTTASK;
[...]
Localization .txt file:
MYOPTTASK Deal 500K damage
- TASKOPTCOMPLETE [id]
Marks optional task with specified id as completed.
- TASKADD [id] [addtaskname]
Adds an optional task without checkbox to tasks box. Task naem should correspond to a string defined in localization file.
Example usage ►Instructions .dat file:
[...]
TASKADD 1 MYADDTASK;
[...]
Localization .txt file:
MYADDTASK Look cool
- *MESSAGE [stringname]
Displays a hint message (if no message author is defined in current step) or character's mesasge.
- SAY [username]
Sets message author for current step.
Use PLAYER as author name to display current user's name.
- ADVANCE
Moves tutorial progress bar.
- *DELAY [seconds]
Sets delay in seconds.
Note: you should never use this for running/pausing replays as this hugely depends on end user's computer performance.
- *VICTORY
Sets a requirement for user to win the fight.
- EDITGAME
Enters edit replay mode.
- *PLAYFRAMES [frames]
Plays specified number of frames.
- MOVEPLAYER [player] [jointname] [jointstate]
Changes Tori's or Uke's joint states. All variables must be uppercase.
player can be either TORI or UKE.
jointstate can be FORWARD, BACK, HOLD or RELAX.
You can also use MOVEPLAYER [player] RELAXALL or MOVEPLAYER [player] HOLDALL to mass-change joint states.
Example usage: MOVEPLAYER UKE L_PECS FORWARD; will make Uke contract left pecs.
- *MOVEJOINT [jointname] [jointstate]
Requires user to change joint state on Tori's body and shows a joint pulsing animation.
Example usage: MOVEJOINT L_PECS FORWARD; will require user to change their character's pecs state to "Contracting".
- *MOVEJOINTOPTIONAL [jointname] [jointstate] ?[taskid]
Sets an optional joint state change requirement. Can be tied to an optional task.
Example usage: MOVEJOINTOPTIONAL R_PECS FORWARD 1; will set an optional requirement for user to move their right pecs forwards. When requirement is met, will also mark optional task with id 1 as completed.
- *WAITBUTTON
Requires user to press continue button to proceed to next step.
Button is only made clickable after all other requirements for current step are met.
- JOINTLOCK
Disables mouse controls for joints (unless they are required move by MOVEJOINT).
- JOINTUNLOCK
Enables mouse controls for joints.
- KEYBOARDLOCK
Disables keyboard controls.
- KEYBOARDUNLOCK [keys]
Unlocks specified keyboard keys.
Example usage: KEYBOARDUNLOCK WASDZXC ; enables camera, ZXC joint controls and spacebar.
- SHIFTUNLOCK
Unlocks Right Shift and Left Shift keys.
- OPT [optname] [value]
Sets option values. Same as /opt chat command.
Values are automatically reverted back to user values once tutorial ends.
- PLAYSOUND [id]
Plays a sound with corresponding id. Uses user's custom sounds if present.
- *FAILFRAME [frames]
Restarts current step once the fight reaches specified length in frames.
Uses STEPFALLBACK value if specified.
- *PROCEEDFRAME [frames]
Moves on to next step once the fight reaches specified length in frames.
Uses STEPSKIP value if specified.
- GHOSTMODE [mode]
Sets ghost mode.
Mode values: ALL for both player's ghosts, TORI for player's ghost only, NONE for no ghosts.
- CUSTOMFUNC [funcName]
Runs a custom lua function from tutorialname.lua file.
Using custom Lua functions
As the title suggests, you can use custom lua code within your tutorials.
To do that, you first need to create a [mytutorialname].lua file in same location as other tutorial data files.
This file is loaded with loadfile() on tutorial start; any functions you want to run with .dat file should be put in a global functions table:
local function myFunction(viewElement, reqTable)
echo("Hello world")
end
functions = {
MyFunc = myFunction
}
viewElement and
reqTable arguments are always passed to your function, though you aren't forced to use them.
viewElement is a
UIElement object that exists while current step is active and is destroyed once it's over.
reqTable is step requirements table which holds all requirements to move on to next step (or fall back).
By using reqTable, you can add custom requirements or modify existing requirements.
There are also two hook namespaces that may come in handy:
tbTutorialsCustom and
tbTutorialsCustomStatic . First one is used for hooks that you only want to run while current step is active, second one will keep hooks running until tutorial ends.
Example: Adding fade in/out effect ►Here we add a "transition" requirement to make sure the animation is finished before we move on to next tutorial step.
When animation is finished, we set our requirement's "ready" property to true. We also then use Tutorials:checkRequirements(reqTable) method to check whether all step requirements are met and set the return value as reqTable.ready property - this is important to do to ensure proper step progression.
local INTRO = 1
local OUTRO = -1
local function showOverlay(viewElement, reqTable, out, speed)
local speed = speed or 1
local req = { type = "transition", ready = false }
table.insert(reqTable, req)
if (tbOutOverlay) then
tbOutOverlay:kill()
end
local overlay = UIElement:new({
parent = out and tbTutorialsOverlay or viewElement,
pos = { 0, 0 },
size = { viewElement.size.w, viewElement.size.h },
bgColor = cloneTable(UICOLORWHITE)
})
if (out) then
tbOutOverlay = overlay
end
overlay.bgColor[4] = out and 0 or 1
overlay:addCustomDisplay(true, function()
overlay.bgColor[4] = overlay.bgColor[4] + (out and 0.02 or -0.02) * speed
if (not out and overlay.bgColor[4] <= 0) then
req.ready = true
reqTable.ready = Tutorials:checkRequirements(reqTable)
overlay:kill()
elseif (out and overlay.bgColor[4] >= 1) then
req.ready = true
reqTable.ready = Tutorials:checkRequirements(reqTable)
end
set_color(unpack(overlay.bgColor))
draw_quad(overlay.pos.x, overlay.pos.y, overlay.size.w, overlay.size.h)
end)
end
local function introOverlay(viewElement, reqTable)
showOverlay(viewElement, reqTable)
end
local function outroOverlay(viewElement, reqTable)
showOverlay(viewElement, reqTable, true)
end
functions = {
IntroOverlay = introOverlay,
OutroOverlay = outroOverlay
}
Tutorials, UIElement and UIElement3D classes will be always available for your custom lua code, but if you want to use functions from any other classes/files make sure to include them separately (unless you're 100% sure they're already loaded, but an extra require() won't hurt).
Tutorials class itself is defined in data/script/tutorial/tutorial_manager.lua. If you're looking for more examples of how custom lua code can be used for tutorials, check existing game tutorials (data files are located in data/tutorials and custom lua code for them is located in data/script/tutorial/data).
Running your tutorial
Alright, so you got your files in one place, now you only need to run it as an actual whole thing.
There's no way to load a custom tutorial with one single command with what's included in Toribash now, but here's a quick lua script I wrote that will do it for you:
runTutorial.lua
To launch your tutorial using it, put the data files in data/script/mytutorials/ folder, and then run a
/ls runTutorial.lua [tutorialname] command in game. If you made everything right, your tutorial should now be running!
Last edited by sir; Aug 21, 2019 at 08:14 AM.