VortexEngine:Bool#
Always true on Vortex Engine. See Detecting Vortex Engine.
What Vortex Engine adds to Psych Engine's Lua, with an example for every function. In HScript, game is the PlayState:
every setProperty('name', value) here is game.name = value; there.
How to read the entries: ?name is an optional argument, = value is what it is when left out, and what comes after the last : is what the function returns (Void: nothing).
Everything documented on ShadowMario's Psych Engine Lua Script API page works in Vortex Engine the same way: its functions, callbacks, variables and code snippets. This page only lists what Vortex adds on top of it.
Open the Psych Engine Lua API https://shadowmario.github.io/psychengine.lua/No function matches that search.
The VortexEngine variable is true on Vortex and nil on every other engine, so one script can work on both:
use what only Vortex has when it's there, and something else (or nothing) when it isn't.
function onCreatePost()
if VortexEngine then
setLaneUnderlay(true, 0.7) -- only Vortex has this function
else
debugPrint('Playing on another engine: no lanes')
end
end
On Vortex the dark lanes are turned on; on Psych Engine the script prints a message instead of stopping with an error.
// HScript: reading a variable that doesn't exist is an error on other engines
var isVortex = false;
try { if (VortexEngine == true) isVortex = true; } catch(e) { isVortex = false; }
Read like any of Psych's variables, in Lua and HScript.
Always true on Vortex Engine. See Detecting Vortex Engine.
The player turned on "Play as Opponent": they're hitting the opponent's notes. The sides of the chart don't change: mustPress and goodNoteHit
are still the right side's notes, now played by the computer. Use the notes' playable property for "the notes the player hits".
The health scripts get is still Boyfriend's, as songs are made, except before the song starts and in noteMiss / noteMissPress.
function onCreatePost()
if playAsOpponent then
setProperty('healthDrain', 0) -- the song's drain would hurt the player now
end
end
Turns off the Health Drain when the player is playing the opponent's side.
The song is an online match of Vortex's multiplayer.
function onCreatePost()
if onlinePlay then
close() -- this mechanic would be unfair online: the script turns itself off
end
end
The script stops running in online matches and keeps working in normal ones.
An online match, and this player is the one hosting it.
If a script stops the countdown (a dialogue, a cutscene) and starts it later with startCountdown(), the two players wait for each other and the song starts at the same time for both. Nothing has to be done in the script for it.
function onSongStart()
if onlineHost then
debugPrint('You are hosting this match')
end
end
Shows a message only on the host's screen.
Turns the dark lanes under the player's notes on or off and sets how dark they are, whatever the player's option says. Works at any time of the song.
show Bool true shows the lanes, false hides themalpha Float how dark they are, from 0 (invisible) to 1 (black) (0.5)setLaneUnderlay(true, 0.8)
Shows very dark lanes, for a part of the song with a busy background.
Where the ratings ("Sick!"), the combo numbers and "Combo" pop up. The player's option is only the default.
where String "game" (in the stage, moving and zooming with it) or "hud" (on the HUD, always in the same place)setScoreCamera('hud')
The ratings stay on the screen while the stage's camera moves a lot.
Hides every note, the ones already on the screen too (the "Hidden Notes" gameplay changer), or shows them again.
hide Bool true hides the notes, false shows themfunction onBeatHit()
if curBeat == 128 then setHiddenNotes(true) end
if curBeat == 144 then setHiddenNotes(false) end
end
For 16 beats the notes are invisible: the player hits them by memory.
What the V-Slice style results screen counts, so far in the song.
Returns a table with sick, good, bad, shit, missed, combo, maxCombo, totalNotesHit and totalNotes
function onEndSong()
local t = getTallies()
debugPrint('Sicks: ' .. t.sick .. ' / ' .. t.totalNotes .. ', best combo: ' .. t.maxCombo)
end
Prints how many Sicks the player got and the biggest combo when the song ends.
Shows a V-Slice dialogue in a Psych song. Like startDialogue: call it in onStartCountdown (returning Function_Stop) or onEndSong, and the song starts (or ends) when the dialogue is over.
name String the conversation's file: data/dialogue/conversations/NAME.json of the modReturns true if the conversation was found
local talked = false
function onStartCountdown()
if not talked and startConversation('week1-intro') then
talked = true
return Function_Stop -- the countdown waits for the dialogue
end
return Function_Continue
end
The V-Slice dialogue "week1-intro" plays before the song; the countdown starts when it ends.
Psych's function, with more modes on Vortex: "overlay", "hardlight", "difference" and "invert" really work on Lua sprites, drawn the way V-Slice's cameras draw them.
tag String the Lua spritemode String the blend mode (Psych's ones, plus the 4 above)makeLuaSprite('light', 'stage/light', 0, 0)
setBlendMode('light', 'overlay')
addLuaSprite('light', true)
A light image that brightens and colors what's under it, like a real light, instead of covering it.
A camera of the script's own. Sprites go on it with setObjectCamera(sprite, tag), and it's moved, zoomed and tweened by its tag like the song's cameras
(setProperty('tag.zoom', ...), doTweenZoom, cameraShake...).
tag String the camera's nameabove String the camera it goes right over: "game", "hud", "other" or another Lua camera's tag ('hud')Returns false if the tag is already used
function onCreatePost()
makeLuaCamera('camBG', 'game') -- between the stage and the HUD
makeLuaSprite('fog', 'stage/fog', 0, 0)
setObjectCamera('fog', 'camBG')
addLuaSprite('fog', true)
end
function onBeatHit()
setProperty('camBG.zoom', 1.1)
doTweenZoom('fogZoom', 'camBG', 1, 0.4, 'quadOut')
end
A fog layer on its own camera, over the stage and under the HUD, that bops on every beat without moving the stage.
Takes a Lua camera out: what's on it stops being drawn. The song's own cameras can't be removed.
tag String the Lua cameraReturns false if there's no Lua camera with that tag
removeLuaCamera('camBG')
The fog camera of the example above is gone, with the fog.
If there's a camera with that tag.
tag String the camera's nameif not luaCameraExists('camBG') then
makeLuaCamera('camBG', 'game')
end
Makes the camera only once, even if this code runs again.
Turning a camera (setProperty('camGame.angle', 10)) no longer leaves empty corners on Vortex: scripts like zCameraFix aren't needed.
The .frag files of the mod's shaders/ folder, the same ones setSpriteShader uses, on everything a camera shows. They need the player's Shaders option on: with it off these do nothing and return false.
Puts a shader on a camera. A camera can have more than one: they're applied in the order they were added. Its values are set with the usual functions (setShaderFloat, setShaderBool...), using the tag.
camera String "game", "hud", "other" or a Lua camera's tagshader String the shader's file name, without .fragtag String the name its values are set by; using it again replaces the old shader (the shader's name)Returns false if the shader wasn't found or Shaders are off
function onCreatePost()
addCameraShader('game', 'chromatic', 'chroma')
setShaderFloat('chroma', 'amount', 0.005)
end
A chromatic aberration on the whole stage (the HUD isn't affected).
Takes a shader off.
camera String the cameratag String the shader to take off, from whatever camera has it; without it, every shader of the cameraremoveCameraShader('game', 'chroma') -- just that one
removeCameraShader('hud') -- every shader of the HUD
The stage's chromatic aberration is gone, and the HUD is back to normal.
Changes a float of a shader smoothly. onTweenCompleted(tag, shaderTag) is called at the end, and cancelTween(tag) stops it.
tag String the tween's nameshaderTag String a camera shader's tag, or a sprite with a shader (by the sprite's tag)property String the shader's uniform floatvalue Float where it goesduration Float in secondsease String the ease, like Psych's tweens ('linear')function onBeatHit()
setShaderFloat('chroma', 'amount', 0.02)
tweenShaderFloat('chromaOut', 'chroma', 'amount', 0, 0.4, 'quadOut')
end
The chromatic aberration hits hard on every beat and fades away before the next one.
Everything is seen in perspective from the center of the screen. Angles are in degrees.
New properties of Lua sprites, receptors and notes, set with setProperty / setPropertyFromGroup and tweened with startTween. angle still turns it on the screen as always.
angleX Float turned forward/back, around the horizontal line through its originangleY Float turned left/right, around the vertical line through its originz Float pushed away from the screen (it gets smaller, toward the center); negative brings it closerNotes copy them from their receptor, long notes too. To turn a note on its own, set its copy3D to false and then its own values.
function onSongStart()
for i = 0, 3 do
setPropertyFromGroup('opponentStrums', i, 'z', 400) -- far away
startTween('spin' .. i, 'playerStrums.members[' .. i .. ']', {angleY = 360}, 1.5, {ease = 'quadInOut'})
end
end
The opponent's receptors go into the distance, and the player's spin around once, with their notes.
Turns everything a camera shows in perspective, like a card. Turning the HUD tilts the receptors and the notes together (the long notes too), like a 3D modchart. Needs the Shaders option on.
camera String "game", "hud", "other" or a Lua camera's tagangleX Float leaning forward/back (0)angleY Float turned left/right (0)z Float pushed away (0)All zeros put it back to normal. Returns false if it couldn't be turned (Shaders off)
setCamera3D('hud', 30) -- the HUD leans back
setCamera3D('hud') -- back to normal
The receptors and notes lean back like a road going into the distance, then go back to flat.
How a camera is turned now.
camera String the cameraReturns a table with angleX, angleY and z (zeros if it isn't turned)
local hud = getCamera3D('hud')
setCamera3D('hud', hud.angleX + 5, hud.angleY, hud.z)
Leans the HUD 5 degrees more than it already was.
setCamera3D little by little, from how the camera is now. onTweenCompleted(tag, camera) is called at the end, and cancelTween(tag) stops it.
tag String the tween's namecamera String the cameraangleX, angleY, z Float where it goesduration Float in secondsease String the ease ('linear')function onBeatHit()
if curBeat % 8 == 0 then
tweenCamera3D('sway', 'hud', 0, 20, 0, 1, 'sineInOut')
elseif curBeat % 8 == 4 then
tweenCamera3D('sway', 'hud', 0, -20, 0, 1, 'sineInOut')
end
end
The HUD sways from one side to the other every 4 beats.
How strong the perspective is, for everything 3D (sprites, notes, cameras and models).
distance Float how far the eye is from the screen, in pixels (1000 if never changed, at least 50). Smaller is a stronger perspectivesetPerspective(500)
Things turned in 3D look much deeper, like seen through a wide lens.
Makes a 3D model, 200 pixels on its biggest side. It's put on the screen and handled like a Lua sprite: addLuaSprite(tag, front) (behind or in front of the characters),
setObjectCamera, setProperty, startTween, removeLuaSprite.
tag String the model's namemodel String models/NAME.obj of the mod (just "NAME" works)texture String an image of images/, painted with the model's texture coordinates (none: plain white, tinted by color)x, y Float where its center goes (0)Returns false if the model wasn't found or has no triangles that could be read
Its properties, besides x, y, z, angleX, angleY, angle, alpha, color, visible and scrollFactor:
modelScale Float pixels per unit of the modellighting Bool each triangle is shaded by how much it faces the light (true)ambient Float how dark the side away from the light gets, 0 to 1 (0.35)cullBack Bool triangles facing away aren't drawn; turn it off for open or flat models (true)triangleCount Int how many triangles it has (read only)function onCreatePost()
makeLuaModel('ring', 'ring', 'ringTexture', 600, 300)
setProperty('ring.angleX', 20)
addLuaSprite('ring', false) -- behind the characters
end
function onUpdate(elapsed)
setProperty('ring.angleY', getProperty('ring.angleY') + elapsed * 90)
end
A textured ring (mods/MyMod/models/ring.obj with images/ringTexture.png) leaning forward and spinning a quarter turn per second, behind the characters.
The color of one part of a model: the parts its .obj names with usemtl (a tree's trunk and leaves). A model with no texture gets them from the .mtl file its .obj names (mtllib, next to it); without one every part is white, and this colors them.
tag String the modelmaterial String the material's name, as in the .objcolor String the color, like '6B4226'Returns false if the model has no material with that name (the error lists the ones it has)
function onCreatePost()
makeLuaModel('tree', 'tree', '', getMidpointX('dad') - 300, getMidpointY('dad') - 80)
setProperty('tree.modelScale', getProperty('tree.modelScale') * 2.5)
setModelMaterialColor('tree', 'Bark', '6B4226') -- the trunk brown
setModelMaterialColor('tree', 'Tree', '3E9E4F') -- the leaves green
addLuaSprite('tree', false)
end
function onUpdate(elapsed)
setProperty('tree.angleY', getProperty('tree.angleY') + elapsed * 20)
end
A low-poly tree with a brown trunk and green leaves next to the opponent, behind the characters, turning slowly.
The names of a model's materials, to know what to color.
tag String the modelfor _, name in ipairs(getModelMaterials('tree')) do
debugPrint(name)
end
Prints "Bark" and "Tree" for the tree above.
Adds a note to the song, like one of the chart's. A note added for a time that already went by comes as a miss. Note types with a script must already be in the song's chart to have their script loaded.
time Float when it has to be hit, in milliseconds of the songcolumn Int from 0 to getKeyCount() - 1 (0 = left on a 4-key song)mustPress Bool true for the right side's notes (the player's), false for the opponent'sholdLength Float in milliseconds; 0 is not a long note (0)noteType String a note type; '' is a normal note ('')singers String who sings it: "main", "all" or extra characters' tags (the default singers)Returns false if the column doesn't exist on this song
function onBeatHit()
if curBeat == 32 then
for i = 0, 3 do
spawnNote(getSongPosition() + 2000 + i * 250, i, true)
end
spawnNote(getSongPosition() + 3000, 0, true, 800, 'Hurt Note')
end
end
On beat 32, a left-down-up-right staircase comes 2 seconds later, followed by a long Hurt Note on the left.
How many arrows each side has: 4 on most songs, 1 to 9 on songs with extra keys.
for i = 0, getKeyCount() - 1 do
setPropertyFromGroup('playerStrums', i, 'alpha', 0.5)
end
Makes every receptor of the player half see-through, whether the song has 4, 6 or 9 of them.
Characters singing besides the opponent, the player and the girlfriend. They're set up in the chart editor (see the modding guide) or added during the song.
Scripts get each one by its tag, like a Lua sprite: setProperty('pico2.alpha', 0.5), playAnim('pico2', 'singUP', true).
A "Change Character" event with the tag as its first value changes that extra character.
Adds an extra character during the song, the same as one of the chart's: it dances, sings the notes of its side that are marked for it (or all of them with singsAll), holds long notes and misses.
tag String its name for notes and scriptscharacter String the character fileside String "opponent", "player" or "gf" ('opponent')x, y Float where it stands, from the spot of the main character of that side (0)singsAll Bool sings every note of its side (a duet) (false)behind Bool stands behind the main character (false)Returns false if the tag is already used or the character doesn't exist
function onBeatHit()
if curBeat == 64 then
addExtraCharacter('backup', 'pico', 'opponent', -300, 0, true)
playAnim('backup', 'singUP', true)
end
end
On beat 64 Pico shows up next to the opponent, strikes a pose, and then sings every note with them.
Takes an extra character out of the song (one of the chart's too).
tag String the extra characterReturns false if no extra character has that tag
if curBeat == 128 then removeExtraCharacter('backup') end
Pico from the example above leaves on beat 128.
The tags of every extra character of the song.
for _, tag in ipairs(getExtraCharacters()) do
setProperty(tag .. '.alpha', 0.5)
end
Every extra character becomes half see-through, without knowing their tags.
Changes the stage in the middle of the song, like the Change Stage event. The old stage's sprites and scripts go away, the new one is built where the characters are, and they go to its places.
name String the stage (its file in stages, a V-Slice stage, or one the game makes)flags String (optional) separated by commas: keep (the characters stay where they are), snap (the camera is there at once), keepzoom (the zoom stays)Returns false if the stage didn't change (it doesn't exist, it's the one already there, or a script called it off)
function onBeatHit()
if curBeat == 64 then changeStage('school', 'snap') end
end
On beat 64 the song moves to Week 6's school, with the camera there at once.
Whatever a song changes (title, position, size) is put back when the song is left.
Changes the text on the window's title bar.
title String the new titlesetWindowTitle('YOU CAN\'T RUN')
The window's title bar says "YOU CAN'T RUN" until the song is left.
The window's title now.
setWindowTitle(getWindowTitle() .. ' - Hard Mode')
Adds " - Hard Mode" to the end of the title.
Moves the window on the computer's screen.
x, y Int the window's top-left corner, in pixels of the monitorReturns false while the game is fullscreen (it isn't moved)
local baseX, baseY
function onSongStart()
baseX = getWindowX()
baseY = getWindowY()
end
function onBeatHit()
if curBeat >= 64 and curBeat < 96 then
setWindowPosition(baseX + getRandomInt(-15, 15), baseY + getRandomInt(-15, 15))
elseif curBeat == 96 then
setWindowPosition(baseX, baseY)
end
end
The window shakes on every beat from 64 to 96, then goes back where it was.
Where the window's top-left corner is on the monitor.
debugPrint('The window is at ' .. getWindowX() .. ', ' .. getWindowY())
Prints the window's position.
Resizes the window. The game is stretched to fit it, as when the player resizes it.
width Int at least 160height Int at least 90Returns false while the game is fullscreen, or for a size too small
setWindowSize(960, 540)
The window shrinks to 960x540, for a "the game is getting smaller" effect.
The window's size now.
setWindowSize(math.floor(getWindowWidth() * 0.9), math.floor(getWindowHeight() * 0.9))
Makes the window 10% smaller than it is.
The size of the monitor the window is on.
setWindowPosition(math.floor((getScreenWidth() - getWindowWidth()) / 2), math.floor((getScreenHeight() - getWindowHeight()) / 2))
Puts the window in the middle of the monitor.
If the game is fullscreen (then the window can't be moved or resized).
if not isWindowFullscreen() then
setWindowSize(960, 540)
end
Only shrinks the window when the player is playing in a window.
Shows a message box of the system. The game waits until it's closed.
message String the texttitle String the box's title (the window's title)function onEndSong()
windowAlert('You beat me... for now.', 'Sonic.exe')
return Function_Continue
end
A Windows message box shows up when the song ends, before the results.
Puts text on the computer's clipboard (what CTRL+V pastes).
text String the textfunction onEndSong()
setClipboard('I got ' .. getProperty('songScore') .. ' points on ' .. songName .. '!')
return Function_Continue
end
The player's score is copied, ready to paste on Discord.
The text on the computer's clipboard ('' if there's none).
if getClipboard() == 'secret code' then
debugPrint('You found the secret!')
end
A secret that unlocks when the player copied a certain text before the song.
Functions the engine calls on Lua and HScript scripts, like Psych's own (onBeatHit, goodNoteHit...).
A part of the song with 3 seconds or more without notes to hit started. Called even if the player turned the Break Timer off.
seconds Float how long the break is, in real seconds (playback rate included)function onBreakStart(seconds)
doTweenZoom('breakZoom', 'camGame', 0.7, 1, 'quadOut')
end
The camera zooms out during the breaks of the song.
The notes are coming again (or the song ended).
function onBreakEnd()
doTweenZoom('breakZoom', 'camGame', getProperty('defaultCamZoom'), 0.5, 'quadIn')
end
With the example above: the camera zooms back in when the notes come back.
A V-Slice dialogue started with startConversation ended.
name String the conversation that endedfunction onConversationEnd(name)
if name == 'week1-intro' then
playSound('confirmMenu')
end
end
A sound plays when the intro dialogue is over, right before the countdown.
The stage is about to change (the Change Stage event, or changeStage). Return Function_Stop to call it off.
function onChangeStage(newStage, oldStage)
if newStage == 'limo' and not getModSetting('limo') then
return Function_Stop
end
end
The limo only comes if the mod's setting says so.
The stage changed. curStage, defaultBoyfriendX/Y, defaultOpponentX/Y and defaultGirlfriendX/Y already have the new stage's values.
function onChangeStagePost(newStage, oldStage)
cameraFlash('camGame', 'FFFFFF', 0.5)
end
A white flash hides the moment the stage changes.
With setProperty / getProperty. Each one starts with the player's option and can be changed at any time of the song.
| Property | Type | What it is |
|---|---|---|
iconBop | Bool | The health icons bop on the beat. |
sustainWiggle | Bool | The long notes wiggle (needs Shaders on). |
showBreakTimer | Bool | The countdown shown when there are 3 seconds or more without notes. |
ghostNotes | Bool | Two notes of the same character together leave an afterimage of the first pose. |
healthDrain | Float, 0 to 2 | Every note the opponent hits takes health. It never kills: health stops at 0.01. |
speedMiss | Bool | Every miss makes the song 5% faster, up to 2x. |
hiddennotes | Bool | Notes that show up from now on are invisible (setHiddenNotes also hides the ones already there). |
showLaneUnderlay | Bool | Shows or hides the lanes (use setLaneUnderlay to make them when the player's option is off). |
laneUnderlayAlpha | Float, 0 to 1 | How dark the lanes are. |
totalColumns | Int (read) | Arrows per side (same as getKeyCount()). |
isResting | Bool (read) | A break is going on. |
maxRestTime | Float (read) | How long the current or last break is, in seconds. |
maxCombo | Int (read) | The biggest combo of the song. |
scoreCamera | String (read) | "Game" or "HUD" (change it with setScoreCamera). |
isVSliceStage | Bool (read) | The stage is a V-Slice stage. |
playableCharacter | Character | The character the player is playing (Boyfriend, or the opponent with Play as Opponent). |
otherCharacter | Character | The one singing by itself. |
function onSongStart()
setProperty('healthDrain', 1.5)
setProperty('ghostNotes', true)
debugPrint('You are playing ' .. getProperty('playableCharacter.curCharacter'))
end
A harder song: strong health drain and ghost notes, whatever the player's options are.
On notes (getPropertyFromGroup('notes', i, ...)): playable (the player has to hit it, also with Play as Opponent), singers (who sings it), copy3D, angleX, angleY, z.
On the health icons (iconP1 / iconP2): autoUpdate (false: the game stops placing and bopping it), isAnimated and hasWinningIcon (read only).
Vortex's own things on the screen, to move, recolor or hide like any other sprite.
| Name | What it is |
|---|---|
restCountdownTxt, restTimerBar, restportrait | The Break Timer's text, bar and picture. |
keystrokes | The Keystrokes display (null when the option is off). |
laneUnderlays | The lanes (a group). |
camSus, camLanes | The cameras of the long notes and of the lanes (under the HUD's). |
camOverlay | The camera over every other one, with the keystrokes and the Break Timer. |
susWiggle | The wiggle of the long notes: waveAmplitude, waveFrequency, waveSpeed. |
grpHoldCovers | The sparks on the receptors while a long note is held. |
onlineStatsTxt | Online: the other player's name, score, misses and accuracy (null when not online). |
function onCreatePost()
setProperty('restCountdownTxt.color', getColorFromHex('FF66CC'))
setPropertyFromGroup('laneUnderlays', 0, 'color', getColorFromHex('220033'))
setProperty('susWiggle.waveAmplitude', 0.15)
end
A pink Break Timer, a purple left lane, and long notes that wiggle more.
HScript reaches everything above directly (game.spawnNote(...), game.addExtraCharacter(...), FlxG.cameras, FlxG.stage.window), and has a few things of its own:
FlxColor.fromString('0x80FF0000'); // "0xAARRGGBB" and "#RRGGBB", with alpha too
FlxColor.interpolate(color1, color2, 0.5); // a color between two others
FlxColor.gradient(color1, color2, 10); // a list of 10 colors from one to the other
game.playableCharacter; game.otherCharacter; // the one being played and the one singing by itself
game.playableStrums; game.otherStrums; // the receptors of each
game.makeStrumsEarly(); // in onCreatePost: the receptors now, to move them before the song
game.extraCharacters; // a list: .tag, .side, .char
game.getExtraCharacter('pico2').char;
Menus and the loading screen have scripts of their own: see state scripts.
Everything on this page only exists in Vortex Engine. The mods menu has a CHECK MOD button: it reads a mod's scripts, charts and icons and lists everything it uses that only Vortex has, with the file and line of each one.
| Level | Meaning |
|---|---|
| ERROR | Other engines stop the script there (a function only Vortex has). |
| DIFFERENT | It works differently or not at all there (callbacks, properties, blend modes, extra characters). |
| NOTE | Usually fine (a variable that is just nil there, an option that reads as nothing). |
A mod with no ERROR or DIFFERENT should also work on Psych Engine 1.0.4. Checking the VortexEngine variable before using something is how a script can work on both.