Starting with version 5.4, Toribash features a new way to create and run tutorials which doesn't require extensive Lua scripting knowledge.
This guide expands on how to create your own tutorials, what they can be used for and how to run them afterwards.
This guide has been updated as per Toribash 5.60 (build 230522) which brought several changes to how custom tutorials testing works compared to method described in the original guide.
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 in-game event (e.g. Hole in the Wall).
Any tutorial consists of minimum 2 data files, one of which contains tutorial instructions and the other 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 begins automatically.
Creating your first tutorial
Let's say we want to make a simple 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
tutorialname_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 automatically start the replay - it will be 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 clicks "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;
OPT tooltip 1;
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), enable 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
must 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
Below you can see a complete list of all currently available commands as of Toribash 5.60.
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 match.
- * LOADREPLAY [replayname.rpl] ?[cached]
Loads and pauses a replay from replays/system/tutorial/ folder or from replays/ folder if the specified name begins with ../.
To load replay with cache (replay speed will be instantly available but replay will take longer to load), set 1 as cached value.
Example usage:- LOADREPLAY myreplay.rpl 1; will load a replays/system/tutorial/myreplay.rpl replay with cache enabled.
- LOADREPLAY ../mytutorial/begin.rpl; will load a replays/mytutorial/begin.rpl replay with cache disabled.
- LOADPLAYER [id] [username]
Loads user's customs onto the specified player id. To load currently logged in user's customs, specify PLAYER as the username.
Example usage: LOADREPLAY 0 PLAYER;
- 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.
You can see the example of this command's usage 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 Uke joint dismember requirement.
Example usage: DISMEMBER R_KNEE; will require user to dismember Uke's right knee.
- * FRACTURE [jointname]
Sets a Uke fracture requirement.
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.
- SHOWWAITBUTTON
Shows "Continue" button if it was hidden.
- HIDEWAITBUTTON
Hides "Continue" button.
- * TASK [taskname]
Adds a task with checkbox to the tutorial objectives bar. Task name must 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 objectives bar. Task name must 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 additional task (without checkbox) to objectives bar. Task name must 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 message.
- 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 command does not take user's FPS into consideration.
- * VICTORY
Sets a requirement for user to win the fight.
- EDITGAME
Enters replay editing mode.
- * PLAYFRAMES [frames]
Plays specified number of frames.
- MOVEPLAYER [player] [jointname] [jointstate]
Changes Tori's or Uke's joint states.
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
Displays "Continue" button if it was hidden and waits for user to press it to proceed to next step.
Button is only made clickable after all other requirements for current step are met.
- JOINTLOCK
Disables joint controls (unless they are required move by MOVEJOINT).
- JOINTUNLOCK
Enables joint controls.
- 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's initial settings once tutorial ends.
- PLAYSOUND [id]
Plays a sound with corresponding id. Will use user's custom sounds if present.
- * FAILFRAME [frames]
Restarts current step once the fight reaches the specified match frame.
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.
- INTROFADE
Plays intro tutorial transition from white overlay.
- OUTROFADE
Plays outro tutorial transition to white overlay.
- CAMERAMODE [mode]
Sets camera mode to the specified value.
- OPT [option] [value]
Sets a game option value for the duration of the tutorial (or until it's changed in a latter step).
Example usage: OPT hud 0;
- 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 on tutorial launch; any functions you want to run with .dat file should be returned at the end of the file or put in a global
functions table (legacy method):
lua code:
local function myFunction(viewElement, reqTable)
echo("Hello world")
end
return {
MyFunc = myFunction
}
All custom functions will always receive two arguments by default,
viewElement and
reqTable. You can safely ignore them in some cases, but they'll be of great use if you want to expand your tutorial gui or add custom step completion conditions.
- First argument passed to the function is a UIElement object that exists while current step is active and is destroyed once it's over. You'd typically want to attach any custom UI elements to this object as it ensures they'll only exist during current step.
- Second argument is a list of current step requirements (serialized as TutorialStepRequirement[] with EmmyLua annotations) to move on to the next step (or fall back). By adding elements to this list, you can add your own custom requirements or modify existing requirements and/or their completion states.
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.
Here's an example of
tbTutorialsCustom usage to wait for player to press a specific key as it's used in Punching Tutorial:
Code ►lua code:
local function requireKeyPress(viewElement, reqTable, key, show)
local req = { type = "keypress", ready = false }
table.insert(reqTable, req)
local button = nil
if (show) then
local displayKey = key
local width = 100
if (key == " ") then
displayKey = "SPACEBAR"
width = 300
end
button = viewElement:addChild({
pos = { 250 - width / 2, -200 },
size = { width, 70 },
interactive = true,
bgColor = table.clone(TB_MENU_DEFAULT_BG_COLOR),
hoverColor = table.clone(TB_MENU_DEFAULT_LIGHTEST_COLOR),
shapeType = ROUNDED,
rounded = 10
})
button:deactivate()
button.isactive = true
button:addAdaptedText(false, displayKey)
end
add_hook("key_up", "tbTutorialsCustom", function(s, code)
if (string.schar(s) == key or (code > 3 and code < 30 and string.schar(code + 93) == key)) then
if (show and button.hoverState ~= BTN_NONE) then
button.hoverState = BTN_NONE
req.ready = true
reqTable.ready = Tutorials:checkRequirements(reqTable)
elseif (not show) then
req.ready = true
reqTable.ready = Tutorials:checkRequirements(reqTable)
end
end
end)
add_hook("key_down", "tbTutorialsCustom", function(s, code)
if ((string.schar(s) == key or (code > 3 and code < 30 and string.schar(code + 93) == key)) and show) then
button.hoverState = BTN_HVR
end
end)
end
local function requireKeyPressC(viewElement, reqTable)
requireKeyPress(viewElement, reqTable, "c")
end
local function showKeyPressSpace(viewElement, reqTable)
requireKeyPress(viewElement, reqTable, " ", true)
end
return {
RequireKeyPressC = requireKeyPressC,
RequireKeyPressSpace = showKeyPressSpace
}
Tutorials,
UIElement,
UIElement3D,
Files,
MoveMemory and
Tooltip classes will be always available for your custom Lua code, but if you want to use functions from other Toribash classes or other Lua files make sure to include them separately to be sure they're available.
Tutorials class itself is defined in
data/script/system/tutorial_manager.lua.
If you're looking for more examples of how custom Lua code can be used for tutorials or events, feel free to check existing game tutorials (data files are located in
data/tutorials and custom Lua code for them is located in
data/script/tutorial/data) or events (all files located in
data/script/event/).
Running your tutorial
Alright, so you got your files in one place, now you only need to run it as an actual whole thing.
Your whole tutorial can be loaded with a single Lua call:
lua code:
Tutorials:loadTutorial(tutorialName, tutorialPath)
You can also download this script that will allow you to launch the tutorial with a custom name / path:
runTutorial.lua
To use it, run
/ls runTutorial.lua test mytutorials to load "test" tutorial from
data/script/mytutorials folder. Alternatively, you can load the same tutorial by its absolute path by adding the
../ prefix to the path:
/ls runTutorial.lua test ../data/script/mytutorials.
Last edited by sir; May 23, 2023 at 09:36 AM.