Skip to main content

Script hooks

Hooks are plain functions you define in a script. The engine calls the ones it finds and caches the absence of the rest, so unused hooks cost nothing.

Syntax below is Lua; Python is identical with def name(args):, and standalone HScript (.hx/.hscript) with function name(args) { ... } — see docs/TemplateScript.hx for the exact typed signatures.

Return values​

ReturnEffect
nothing / Function_Continuethe engine proceeds normally
Function_Stopcancels the engine's default behaviour for that hook
Function_StopLua (Python/HScript: also Function_StopAll)stops the hook propagating to further scripts

Only hooks documented as cancellable react to Function_Stop.

Lifecycle​

HookWhenCancellable
onCreate()script created; many PlayState variables do not exist yet
onCreatePost()end of create() — the recommended setup point
onDestroy()script ending (song fade-out finished)
onUpdate(elapsed)start of the frame, before engine updates
onUpdatePost(elapsed)end of the frame

Song flow​

HookWhenCancellable
onStartCountdown()before the countdown — stop it to run a dialogue, then call startCountdown()✓
onCountdownStarted()the countdown actually began
onCountdownTick(counter)0=Three, 1=Two, 2=One, 3=Go!, 4=none
onSongStart()inst + vocals start, songPosition = 0
onEndSong()song finished / transition starting✓
onStepHit()16× per section
onBeatHit()4× per section
onSectionHit()once per section

Notes and input​

HookArgumentsNotes
onSpawnNote(id, noteData, noteType, isSustainNote, strumTime)fired as a note enters play
goodNoteHit(id, direction, noteType, isSustainNote)player hit a note
opponentNoteHit(id, direction, noteType, isSustainNote)opponent hit a note
goodNoteSkip(id, direction, noteType, isSustainNote)a player note was skipped (when noSkipFuncs is false)
opponentNoteSkip(id, direction, noteType, isSustainNote)an opponent note was skipped (when noSkipFuncs is false)
noteMiss(id, direction, noteType, isSustainNote)note missed by letting it pass
noteMissPress(direction)ghost miss — pressed with no note
onGhostTap(key)a tap that hit nothing, with ghost tapping on
onKeyPress(key) / onKeyRelease(key)raw lane input

id is the note's member index — read more with getPropertyFromGroup('notes', id, 'strumTime'). Direction: 0 left, 1 down, 2 up, 3 right.

Events​

HookArgumentsNotes
onEvent(name, value1, value2, strumTime)an event note fired (not fired by triggerEvent())
eventEarlyTrigger(name, value1, value2, strumTime)return a millisecond offset to fire the event earlier; overrides the engine's hardcoded values
function eventEarlyTrigger(name, value1, value2, strumTime)
if name == 'Kill Henchmen' then
return 280 -- fire 280ms early so the sound lands on the beat
end
end

Pause, game over, dialogue​

HookArgumentsCancellable
onPause()✓ (block pausing)
onResume()
onGameOver()called every frame health ≤ 0✓ (block the game over)
onGameOverStart()the game over screen opened
onGameOverConfirm(retry)retry is false when the player pressed Esc
onNextDialogue(line)dialogue line index, starting at 1
onSkipDialogue(line)a line was skipped while typing

Camera, rating, score​

HookArgumentsCancellable
onMoveCamera(focus)'boyfriend', 'dad' (or 'gf')
onRecalculateRating()fired before the calculation; stop it and use setRatingPercent() / setRatingName() / setRatingFC() for your own✓
onUpdateScore(miss)the score text is about to update

Tweens, timers, sound​

HookArguments
onTweenCompleted(tag)a tween you started finished
onTimerCompleted(tag, loops, loopsLeft)a timer loop finished
onSoundFinished(tag)a sound you started finished

Custom substates​

Scripts can open their own substate with openCustomSubstate(name, pauseGame). That helper now also loads data/states/<name>.hx if present, and works outside PlayState. Full-screen replacements use switchCustomState(name, data) / openModSubState(name, data) — see Scripted states.

Hook
onCustomSubstateCreate(name) / onCustomSubstateCreatePost(name)
onCustomSubstateUpdate(name, elapsed) / onCustomSubstateUpdatePost(name, elapsed)
onCustomSubstateDestroy(name)

Full engine list​

These are the hook names PlayState and its helpers broadcast:

onCreate onCreatePost onDestroy onUpdate onUpdatePost
onStartCountdown onCountdownStarted onCountdownTick onSongStart
onStepHit onBeatHit onSectionHit onEndSong
onSpawnNote goodNoteHit opponentNoteHit goodNoteSkip opponentNoteSkip
noteMiss noteMissPress onGhostTap onKeyPress onKeyRelease
onEvent eventEarlyTrigger onPause onResume
onGameOver onGameOverStart onGameOverConfirm onNextDialogue onSkipDialogue
onMoveCamera onRecalculateRating onUpdateScore onSoundFinished
onTweenCompleted onTimerCompleted onCustomSubstateCreate onCustomSubstateCreatePost
onCustomSubstateUpdate onCustomSubstateUpdatePost onCustomSubstateDestroy
Keep it cheap

onUpdate and onUpdatePost run every frame for every loaded script. Move work into beat hooks, timers or event handlers whenever the timing allows.