Friday Night Funkin’ uses HScript for mod scripting - a scripting language that looks and works like Haxe. Scripts can extend classes, react to events, and modify gameplay in real-time.
HScript syntax is nearly identical to Haxe. If you know Haxe, you already know HScript!
// Control whether module receives eventsthis.active = true; // or false// Change event prioritythis.priority = 500;// Restrict to specific statethis.state = flixel.FlxState; // Only active in this state
public function onCreate(event:ScriptEvent){ // Called when module is first created (before title screen)}public function onDestroy(event:ScriptEvent){ // Called when module is destroyed (F5 reload)}public function onUpdate(event:UpdateScriptEvent){ // Called every frame // event.elapsed = time since last frame}
public function onStateChangeBegin(event:StateChangeScriptEvent){ // About to switch to new state // event.targetState = the new state}public function onStateChangeEnd(event:StateChangeScriptEvent){ // Switched to new state}public function onSubStateOpenBegin(event:SubStateScriptEvent){ // About to open a substate}
public function onFocusGained(event:FocusScriptEvent){ // Game window gained focus}public function onFocusLost(event:FocusScriptEvent){ // Game window lost focus}
public function onSongLoaded(event:SongLoadScriptEvent){ // Song chart parsed, before notes placed // Modify event.notes or event.events to change chart}public function onSongStart(event:ScriptEvent){ // Song started (conductor time = 0)}public function onSongEnd(event:ScriptEvent){ // Song ended, about to unload}public function onSongRetry(event:SongRetryEvent){ // Player restarted song // event.difficulty = new difficulty}
public function onNoteIncoming(event:NoteScriptEvent){ // Note entered view and approaches strumline // event.note = the note sprite}public function onNoteHit(event:HitNoteScriptEvent){ // Note was hit (player or CPU) // event.note = the note // event.judgement = "sick", "good", "bad", "shit" // event.score = points awarded // event.healthChange = health gained/lost // event.doesNotesplash = whether splash shows}public function onNoteMiss(event:NoteScriptEvent){ // Note was missed // event.healthChange = health lost}public function onNoteGhostMiss(event:GhostMissNoteScriptEvent){ // Player pressed key with no note // event.dir = direction pressed // event.healthChange = health penalty}public function onNoteHoldDrop(event:HoldNoteScriptEvent){ // Player dropped a hold note}
public function onStepHit(event:SongTimeScriptEvent){ // Every step (16th note) // event.step = current step number // event.beat = current beat number}public function onBeatHit(event:SongTimeScriptEvent){ // Every beat (quarter note) // event.beat = current beat}
public function onCountdownStart(event:CountdownScriptEvent){ // Countdown starting}public function onCountdownStep(event:CountdownScriptEvent){ // Each countdown step (3, 2, 1, Go) // event.step = countdown step enum}public function onCountdownEnd(event:CountdownScriptEvent){ // Countdown finished, song about to start}
public function onCapsuleSelected(event:CapsuleScriptEvent){ // Song capsule selected in Freeplay}public function onDifficultySwitch(event:CapsuleScriptEvent){ // Difficulty changed}public function onSongSelected(event:CapsuleScriptEvent){ // Song confirmed}
public function onCharacterSelect(event:CharacterSelectScriptEvent){ // Character selected // event.characterId = character ID}public function onCharacterConfirm(event:CharacterSelectScriptEvent){ // Character confirmed}
public function onPause(event:PauseScriptEvent){ // Game paused // event.gitaroo = use gitaroo man pause?}public function onResume(event:ScriptEvent){ // Game resumed}
public function onCountdownStart(event:CountdownScriptEvent){ // Prevent countdown from starting event.cancel(); // Stop other scripts from receiving this event event.stopPropagation();}
Only cancelable events can be cancelled. Check event.cancelable before calling cancel().
Scripts can implement the IScriptedClass interface:
interface IScriptedClass{ public function onScriptEvent(event:ScriptEvent):Void; public function onCreate(event:ScriptEvent):Void; public function onDestroy(event:ScriptEvent):Void; public function onUpdate(event:UpdateScriptEvent):Void;}
Specialized interfaces add more events:
IPlayStateScriptedClass - All gameplay events
IStateChangingScriptedClass - State transition events
import funkin.modding.module.Module;class ScoreMultiplier extends Module{ public function new() { super('scoremultiplier'); } public function onNoteHit(event:HitNoteScriptEvent) { // Double the score for sick notes if (event.judgement == 'sick') { event.score *= 2; trace('Score doubled!'); } }}
import funkin.modding.module.Module;import flixel.FlxG;class CustomCountdown extends Module{ public function new() { super('customcountdown'); } public function onCountdownStep(event:CountdownScriptEvent) { // Play custom sound on "Go!" if (event.step == CountdownStep.THREE) { FlxG.sound.play(Paths.sound('customGo')); } }}
import funkin.modding.module.Module;import funkin.play.PlayState;class HealthDrain extends Module{ var drainRate:Float = 0.01; public function new() { super('healthdrain'); } public function onUpdate(event:UpdateScriptEvent) { // Only drain in PlayState if (Std.is(FlxG.state, PlayState)) { var playState = cast(FlxG.state, PlayState); playState.health -= drainRate * event.elapsed; } }}
public static function register(id:String, ?data:Dynamic):Dynamicpublic static function get(id:String):Null<Dynamic>public static function remove(id:String):Null<Dynamic>
Usage:
import funkin.modding.ModStore;// Register a storevar myData = ModStore.register('mymod', { score: 0 });// Access from any scriptvar data = ModStore.get('mymod');data.score += 100;// Clean up when doneModStore.remove('mymod');