diff --git a/assets/data/stages/philly.hx b/assets/data/stages/philly.hx index eb1843a5..7403ddd5 100644 --- a/assets/data/stages/philly.hx +++ b/assets/data/stages/philly.hx @@ -1,9 +1,29 @@ -var lights:FlxSprite; +var curColor:Int = 0; +var colors = [ + 0xFF31A2FD, + 0xFF31FD8C, + 0xFFFB33F5, + 0xFFFD4531, + 0xFFFBA633 +]; function create() { - lights = stage.getSprite("") + light.color = colors[curColor]; +} + +function beatHit(curBeat:Int) { + if (curBeat % 4 == 0) { + // switches color + var newColor = FlxG.random.int(0, colors.length-2); + if (newColor >= curColor) newColor++; + curColor = newColor; + light.color = colors[curColor]; + } } function update(elapsed:Float) { - + if (Conductor.songPosition > 0) + light.alpha = 1 - (FlxEase.cubeIn((curBeatFloat / 4) % 1) * 0.85); + else + light.alpha = 0; } \ No newline at end of file diff --git a/assets/data/stages/philly.xml b/assets/data/stages/philly.xml index 1c98c4d3..1b7c0faf 100644 --- a/assets/data/stages/philly.xml +++ b/assets/data/stages/philly.xml @@ -2,7 +2,7 @@ - + diff --git a/buildnumber.txt b/buildnumber.txt index 725cdcde..e0d1ea1e 100644 --- a/buildnumber.txt +++ b/buildnumber.txt @@ -1 +1 @@ -193 \ No newline at end of file +205 \ No newline at end of file diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index ff8e88db..1cb40a9c 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -1,10 +1,12 @@ package funkin.game; +import flixel.util.FlxDestroyUtil; #if desktop import Discord.DiscordClient; #end import Section.SwagSection; import funkin.system.Song.SwagSong; +import funkin.scripting.ScriptPack; import WiggleEffect.WiggleEffectType; import flixel.FlxBasic; import flixel.FlxCamera; @@ -62,6 +64,7 @@ class PlayState extends MusicBeatState public static var storyDifficulty:Int = 1; public static var fromMods:Bool = false; + public var scripts:ScriptPack; public var halloweenLevel:Bool = false; public var stage:Stage; @@ -134,6 +137,9 @@ class PlayState extends MusicBeatState if (FlxG.sound.music != null) FlxG.sound.music.stop(); + scripts = new ScriptPack("PlayState"); + scripts.setParent(this); + camGame = new FlxCamera(); camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; @@ -190,11 +196,15 @@ class PlayState extends MusicBeatState // TODO: Custom Stage Loading add(new Stage(SONG.stage)); + scripts.load(); + scripts.call("create"); + add(gf); add(dad); add(boyfriend); + strumLine = new FlxSprite(0, 50).makeGraphic(FlxG.width, 10); strumLine.scrollFactor.set(); @@ -240,6 +250,9 @@ class PlayState extends MusicBeatState scoreTxt = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, "", 20); scoreTxt.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, RIGHT); + scoreTxt.borderStyle = OUTLINE; + scoreTxt.borderSize = 1; + scoreTxt.borderColor = 0xFF000000; scoreTxt.scrollFactor.set(); add(scoreTxt); @@ -263,21 +276,25 @@ class PlayState extends MusicBeatState public override function createPost() { super.createPost(); startCountdown(); + scripts.call("createPost"); } public var startTimer:FlxTimer; public var perfectMode:Bool = false; + public var introLength:Int = 5; function startCountdown():Void { + inCutscene = false; generateStaticArrows(0); generateStaticArrows(1); + scripts.call("onStartCountdown"); startedCountdown = true; Conductor.songPosition = 0; - Conductor.songPosition -= Conductor.crochet * 5; + Conductor.songPosition -= Conductor.crochet * introLength; var swagCounter:Int = 0; @@ -304,6 +321,9 @@ class PlayState extends MusicBeatState } } + // TODO: Cancelable countdown + scripts.call("onCountdown", [swagCounter]); + switch (swagCounter) { @@ -368,7 +388,7 @@ class PlayState extends MusicBeatState swagCounter += 1; // generateSong('fresh'); - }, 5); + }, introLength); } public var previousFrameTime:Int = 0; @@ -391,7 +411,9 @@ class PlayState extends MusicBeatState } public override function destroy() { + scripts.call("destroy"); super.destroy(); + FlxDestroyUtil.destroy(scripts); instance = null; } @@ -603,6 +625,8 @@ class PlayState extends MusicBeatState override function openSubState(SubState:FlxSubState) { + // TODO: Cancellable substate opening. + scripts.call("onSubstateOpen", [SubState]); if (paused) { if (FlxG.sound.music != null) @@ -620,6 +644,7 @@ class PlayState extends MusicBeatState override function closeSubState() { + scripts.call("onSubstateClose"); if (paused) { if (FlxG.sound.music != null && !startingSong) @@ -648,6 +673,7 @@ class PlayState extends MusicBeatState override public function onFocus():Void { + scripts.call("onFocus"); #if desktop if (health > 0 && !paused) { @@ -667,6 +693,7 @@ class PlayState extends MusicBeatState override public function onFocusLost():Void { + scripts.call("onFocusLost"); #if desktop if (health > 0 && !paused) { @@ -685,6 +712,7 @@ class PlayState extends MusicBeatState Conductor.songPosition = FlxG.sound.music.time; vocals.time = Conductor.songPosition; vocals.play(); + scripts.call("onVocalsResync"); } public var paused:Bool = false; @@ -693,6 +721,8 @@ class PlayState extends MusicBeatState public function pauseGame() { + // TODO: Cancellable game pause + scripts.call("onGamePause"); persistentUpdate = false; persistentDraw = true; paused = true; @@ -711,6 +741,7 @@ class PlayState extends MusicBeatState // TODO: Update Discord Status public function updateDiscordStatus() { + // TODO: Cancellable Discord Update Presence #if desktop // Song duration in a float, useful for the time left feature songLength = FlxG.sound.music.length; @@ -718,6 +749,7 @@ class PlayState extends MusicBeatState // Updating Discord Rich Presence (with Time Left) DiscordClient.changePresence(detailsText, SONG.song + " (" + storyDifficultyText + ")", iconRPC, true, songLength); #end + scripts.call("onDiscordPresenceUpdate"); } override public function update(elapsed:Float) { @@ -726,6 +758,7 @@ class PlayState extends MusicBeatState #end super.update(elapsed); + scripts.call("update", [elapsed]); scoreTxt.text = "Score:" + songScore; @@ -988,6 +1021,7 @@ class PlayState extends MusicBeatState function endSong():Void { + scripts.call("onSongEnd"); canPause = false; FlxG.sound.music.volume = 0; vocals.volume = 0; @@ -1223,6 +1257,8 @@ class PlayState extends MusicBeatState var justPressed = [controls.LEFT_P, controls.DOWN_P, controls.UP_P, controls.RIGHT_P]; var justReleased = [controls.LEFT_R, controls.DOWN_R, controls.UP_R, controls.RIGHT_R]; + // TODO: Events like param:OnKeyEvent for better compatibility + scripts.call("onKeyShit", [pressed, justPressed, justReleased]); var funcsToExec:ArrayVoid> = []; if (pressed.contains(true)) { funcsToExec.push(function(note:Note) { @@ -1254,6 +1290,7 @@ class PlayState extends MusicBeatState playerStrums.forEach(function(str:Strum) { str.updatePlayerInput(pressed[str.ID], justPressed[str.ID], justReleased[str.ID]); }); + scripts.call("onKeyShitPost"); } function noteMiss(direction:Int = 1):Void @@ -1300,6 +1337,8 @@ class PlayState extends MusicBeatState if (!note.wasGoodHit) { if (note.mustPress) { + // TODO: See TODO above + scripts.call("onPlayerHit", [note]); if (!note.isSustainNote) { popUpScore(note.strumTime); @@ -1330,6 +1369,8 @@ class PlayState extends MusicBeatState } }); } else { + // TODO: See TODO above the todo above + scripts.call("onDadHit", [note]); // TODO: NOTE PRESSED HANDLER FOR DAD switch (note.noteData) { @@ -1358,6 +1399,8 @@ class PlayState extends MusicBeatState } public function deleteNote(note:Note) { + // TODO: See TODO above the TODO above the one that says TODO note pressed handler for dad + scripts.call("onNoteDelete", [note]); note.kill(); notes.remove(note, true); note.destroy(); @@ -1366,6 +1409,7 @@ class PlayState extends MusicBeatState override function stepHit() { super.stepHit(); + scripts.call("stepHit", [curStep]); if (FlxG.sound.music.time > Conductor.songPosition + 20 || FlxG.sound.music.time < Conductor.songPosition - 20) { resyncVocals(); @@ -1378,6 +1422,7 @@ class PlayState extends MusicBeatState override function beatHit() { super.beatHit(); + scripts.call("beatHit", [curBeat]); if (SONG.notes[Math.floor(curStep / 16)] != null) { diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index e83db717..b2530bf3 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -5,9 +5,12 @@ import openfl.utils.Assets; import haxe.xml.Access; import flixel.FlxBasic; import funkin.interfaces.IBeatReceiver; +import funkin.scripting.Script; +import haxe.io.Path; class Stage extends FlxBasic implements IBeatReceiver { public var stageXML:Access; + public var stagePath:String; public var stageSprites:Map = []; public var stageScript:Script; @@ -18,8 +21,9 @@ class Stage extends FlxBasic implements IBeatReceiver { public function new(stage:String) { super(); if (PlayState.instance == null) return; + stagePath = Paths.xml('stages/$stage'); try { - stageXML = new Access(Xml.parse(Assets.getText(Paths.xml('stages/$stage'))).firstElement()); + stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement()); } catch(e) { // TODO: handler trace(e.details()); @@ -98,6 +102,11 @@ class Stage extends FlxBasic implements IBeatReceiver { PlayState.instance.add(PlayState.instance.dad); } } + stageScript = Script.create(Paths.script('data/stages/$stage')); + for(k=>e in stageSprites) { + stageScript.set(k, e); + } + PlayState.instance.scripts.add(stageScript); } public function beatHit(curBeat:Int) {} diff --git a/source/funkin/mods/ModsFolder.hx b/source/funkin/mods/ModsFolder.hx index 45a1ca20..a53bf255 100644 --- a/source/funkin/mods/ModsFolder.hx +++ b/source/funkin/mods/ModsFolder.hx @@ -33,14 +33,14 @@ class ModsFolder { public static var modsPath:String = "./mods/"; /** - * [Description] Initialises `mods` folder by adding callbacks and such. + * Initialises `mods` folder by adding callbacks and such. */ public static function init() { FlxG.signals.preStateCreate.add(onStateSwitch); } /** - * [Description] Loads a mod with the specified name. + * Loads a mod with the specified name. * @param modName Name of the mod * @param force Whenever the mod should be reloaded if it has already been loaded */ @@ -74,7 +74,7 @@ class ModsFolder { } /** - * [Description] Gets all assets in folders and put them in the `assets` array. + * Gets all assets in folders and put them in the `assets` array. * @param assets Array of assets * @param rootPath Root path * @param path Path inside of the rootPath (ex: `root path/path/path2/`) @@ -138,7 +138,6 @@ class ModsFolder { } private static function onStateSwitch(newState:FlxState) { - // TODO: assets reloading Assets.cache.clear(); if (currentModFolder == null) return; diff --git a/source/funkin/scripting/HScript.hx b/source/funkin/scripting/HScript.hx index 84f57885..51cd1a04 100644 --- a/source/funkin/scripting/HScript.hx +++ b/source/funkin/scripting/HScript.hx @@ -6,6 +6,8 @@ import hscript.*; class HScript extends Script { var interp:Interp; + var expr:Expr; + public override function onCreate(path:String) { super.onCreate(path); @@ -14,14 +16,12 @@ class HScript extends Script { var code:String = Assets.getText(path); var parser = new hscript.Parser(); parser.allowJSON = parser.allowMetadata = parser.allowTypes = true; - var expr:Expr; try { expr = parser.parseString(code, fileName); } catch(e) { return; } interp.errorHandler = _errorHandler; - interp.execute(expr); } private function _errorHandler(error:Error) { @@ -32,6 +32,10 @@ class HScript extends Script { interp.scriptObject = parent; } + public override function onLoad() { + interp.execute(expr); + } + private override function onCall(funcName:String, parameters:Array):Dynamic { if (interp == null) return null; diff --git a/source/funkin/scripting/Script.hx b/source/funkin/scripting/Script.hx index 02b2cf24..965e5238 100644 --- a/source/funkin/scripting/Script.hx +++ b/source/funkin/scripting/Script.hx @@ -1,31 +1,91 @@ package funkin.scripting; +import flixel.util.FlxDestroyUtil.IFlxDestroyable; +import flixel.FlxBasic; import haxe.io.Path; import openfl.utils.Assets; /** * Class used for scripting. */ -class Script { +class Script extends FlxBasic implements IFlxDestroyable { + public static function getDefaultVariables():Map { + return [ + // Haxe related stuff + "Std" => Std, + "Math" => Math, + "StringTools" => StringTools, + "Json" => haxe.Json, + + // OpenFL & Lime related stuff + "Assets" => openfl.utils.Assets, + "Application" => lime.app.Application, + "window" => lime.app.Application.current.window, + + // Flixel related stuff + "FlxG" => flixel.FlxG, + "FlxSprite" => flixel.FlxSprite, + "FlxEase" => flixel.tweens.FlxEase, + "FlxTween" => flixel.tweens.FlxTween, + "FlxCamera" => flixel.FlxCamera, + "state" => flixel.FlxG.state, + "FlxSound" => flixel.system.FlxSound, + "FlxAssets" => flixel.system.FlxAssets, + "FlxMath" => flixel.math.FlxMath, + "FlxPoint" => flixel.math.FlxPoint, + "FlxGroup" => flixel.group.FlxGroup, + "FlxTypedGroup" => flixel.group.FlxGroup.FlxTypedGroup, + "FlxSpriteGroup" => flixel.group.FlxSpriteGroup, + "FlxTypeText" => flixel.addons.text.FlxTypeText, + "FlxText" => flixel.text.FlxText, + "FlxAxes" => flixel.util.FlxAxes, + "FlxTimer" => flixel.util.FlxTimer, + + // Engine related stuff + "engine" => { + build: funkin.macros.BuildCounterMacro.getBuildNumber(), + name: "Codename Engine" + }, + "PlayState" => funkin.game.PlayState, + "GameOverSubstate" => funkin.game.GameOverSubstate, + "Note" => funkin.game.Note, + "Strum" => funkin.game.Strum, + "Character" => funkin.game.Character, + "Boyfriend" => funkin.game.Character, // for compatibility + "FreeplayState" => funkin.menus.FreeplayState, + "MainMenuState" => funkin.menus.MainMenuState, + "PauseSubState" => funkin.menus.PauseSubState, + "StoryMenuState" => funkin.menus.StoryMenuState, + "TitleState" => funkin.menus.TitleState, + "Paths" => funkin.system.Paths, + "Conductor" => funkin.system.Conductor, + "CoolUtil" => funkin.system.CoolUtil, + ]; + } /** - * [Description] All available script extensions + * All available script extensions */ public static var scriptExtensions:Array = [ "hx", "hscript", "hsc" ]; /** - * [Description] Currently executing script. + * Currently executing script. */ public static var curScript:Script = null; /** - * [Description] Script name (with extension) + * Script name (with extension) */ public var fileName:String; /** - * [Description] Creates a script from the specified asset path. The language is automatically determined. + * Path to the script. + */ + public var path:String = null; + + /** + * Creates a script from the specified asset path. The language is automatically determined. * @param path Path in assets */ public static function create(path:String):Script { @@ -41,26 +101,33 @@ class Script { } /** - * [Description] Creates a new instance of the script class. + * Creates a new instance of the script class. * @param path */ public function new(path:String) { + super(); + fileName = Path.withoutDirectory(path); - var oldScript = curScript; - curScript = this; + this.path = path; onCreate(path); - curScript = oldScript; + for(k=>e in getDefaultVariables()) { + set(k, e); + } } /** - * [Description] Internal. Creates the script. - * @param path Path to the script. + * Loads the script */ - public function onCreate(path:String) {} + public function load() { + var oldScript = curScript; + curScript = this; + onLoad(); + curScript = oldScript; + } /** - * [Description] Calls the function `func` defined in the script. + * Calls the function `func` defined in the script. * @param func Name of the function * @param parameters (Optional) Parameters of the function. * @return Dynamic Result (if void, then null) @@ -76,27 +143,27 @@ class Script { } /** - * [Description] Sets a script's parent object so that its properties can be accessed easily. Ex: Passing `PlayState.instace` will allow `boyfriend` to be typed instead of `PlayState.instance.boyfriend`. + * Sets a script's parent object so that its properties can be accessed easily. Ex: Passing `PlayState.instace` will allow `boyfriend` to be typed instead of `PlayState.instance.boyfriend`. * @param variable Parent variable. */ public function setParent(variable:Dynamic) {} /** - * [Description] Gets the variable `variable` from the script's variables. + * Gets the variable `variable` from the script's variables. * @param variable Name of the variable. * @return Dynamic Variable (or null if it doesn't exists) */ public function get(variable:String):Dynamic {return null;} /** - * [Description] Gets the variable `variable` from the script's variables. + * Gets the variable `variable` from the script's variables. * @param variable Name of the variable. * @return Dynamic Variable (or null if it doesn't exists) */ public function set(variable:String, value:Dynamic):Void {} /** - * [Description] Shows an error from this script. + * Shows an error from this script. * @param text Text of the error (ex: Null Object Reference). * @param additionalInfo Additional information you could provide. */ @@ -111,4 +178,14 @@ class Script { private function onCall(func:String, parameters:Array):Dynamic { return null; } + public function onCreate(path:String) {} + + public function onLoad() {} + + public function onDestroy() {}; + + public override function destroy() { + super.destroy(); + onDestroy(); + } } diff --git a/source/funkin/scripting/ScriptPack.hx b/source/funkin/scripting/ScriptPack.hx new file mode 100644 index 00000000..4caade79 --- /dev/null +++ b/source/funkin/scripting/ScriptPack.hx @@ -0,0 +1,61 @@ +package funkin.scripting; + +class ScriptPack extends Script { + public var scripts:Array