diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4f1084dc..1efcfcfa 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -38,12 +38,12 @@ jobs: - name: Tar files run: tar -zcvf CodenameEngine.tar.gz -C export/release/linux/bin . - name: Uploading artifact (entire build) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Codename Engine path: CodenameEngine.tar.gz # - name: Uploading artifact (executable) - # uses: actions/upload-artifact@v2 + # uses: actions/upload-artifact@v4 # with: # name: Update-Linux # path: export/release/linux/bin/CodenameEngine diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index c11a793f..cdd60670 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -35,12 +35,12 @@ jobs: - name: Tar files run: tar -zcvf CodenameEngine.tar.gz -C export/release/macos/bin . - name: Uploading artifact (entire build) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Codename Engine path: CodenameEngine.tar.gz # - name: Uploading artifact (executable) - # uses: actions/upload-artifact@v2 + # uses: actions/upload-artifact@v4 # with: # name: Update-Mac # path: export/release/macos/bin/CodenameEngine diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3ec71f90..2986be18 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -33,12 +33,12 @@ jobs: run: | haxelib run lime build windows - name: Uploading artifact (entire build) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Codename Engine path: export/release/windows/bin # - name: Uploading artifact (executable) - # uses: actions/upload-artifact@v2 + # uses: actions/upload-artifact@v4 # with: # name: Update-Windows # path: export/release/windows/bin/CodenameEngine.exe diff --git a/BETAMESSAGE.md b/BETAMESSAGE.md index 38685be3..cf86b374 100644 --- a/BETAMESSAGE.md +++ b/BETAMESSAGE.md @@ -35,7 +35,7 @@ Shoutout to the people who stuck with us throughout the alpha and supported us! - ### New Features - Opponent & Co-op modes - New volume change SFX (no more loud flixel beep, customizable) - - New FPS counter allowing you to see advanced info by pressing F3. + - New FPS counter allowing you to see advanced info by pressing F3 (infos can be selected and copied too when in advanced). - (Windows) FNF is no longer blurry on 125-150% DPI - FNF auto fixes audio on state change when you plug in/out your headphones. - FNF auto applies dark titlebar diff --git a/FEATURES.md b/FEATURES.md index 9d53fcaf..caa85417 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -17,7 +17,7 @@ _**QOL = Quality of Life**_ - Usage of [flxanimate](https://github.com/Dot-Stuff/flxanimate) on big sprites, such as Girlfriend to save memory. - You can further optimize it on certain stages by enabling `Low Memory Mode` in `Options > Appearance`. - New volume change SFX (no more loud flixel beep, customizable) -- New FPS counter allowing you to see advanced info by pressing F3. +- New FPS counter allowing you to see advanced info by pressing F3 (infos can be selected and copied too when in advanced). - Simple but advanced modding system (press TAB on main menu) - **Windows only:** - (Windows) FNF is no longer blurry on 125-150% DPI diff --git a/README.md b/README.md index 604cd9c2..b4d4bcc9 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Make sure to read the wiki and join the Discord server linked there!
Latest builds for the engine can be found in the [Actions](https://github.com/YoshiCrafter29/CodenameEngine/actions) tab.
In the future (when the engine won't be a WIP anymore) we're gonna also publish the engine on platforms like Gamebanana; stay tuned! +If you don't have a github account please go onto https://fnf-cne-devs.github.io/ and click the download button for the respective operating system. +

How to build

@@ -76,7 +78,7 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish - Steal code from Codename Engine for another different project that is not Codename Engine related (Codename Engine mods excluded) without properly crediting - Release the entire Codename Engine on platforms (Mods that use Codename Engine as source are fine, if it's specified even better) - #### *If you need more info or feel like asking to do something which is not listed here, ask us directly on our discord (linked in the wiki)!* + #### *If you need more info or feel like asking to do something which is not listed here, ask us directly on our discord (linked on the website)!*
diff --git a/art/flashFiles/Editor_Icons.fla b/art/flashFiles/Editor_Icons.fla new file mode 100644 index 00000000..6ad80d3d Binary files /dev/null and b/art/flashFiles/Editor_Icons.fla differ diff --git a/art/generateDoc.bat b/art/generateDoc.bat index 5159ca88..e2d35e4f 100644 --- a/art/generateDoc.bat +++ b/art/generateDoc.bat @@ -1,7 +1,8 @@ @ECHO OFF cd .. echo Building Game... -lime build windows --haxeflag="--macro include('scripting')" --haxeflag="-xml docs/doc.xml" -echo Generating Documentation... -haxelib run dox -i docs --include funkin --include scripting -echo Done! \ No newline at end of file +lime build windows --haxeflag="--macro include('scripting')" --haxeflag="-xml docs/doc.xml" -D DOCUMENTATION --no-output +echo art + +echo Generated the api xml file at docs/doc.xml +echo Please put this in FNF-CNE-Devs.github.io/api-generator/api/doc.xml \ No newline at end of file diff --git a/assets/data/characters/bf-car.xml b/assets/data/characters/bf-car.xml index b4c9b24c..bd276ca8 100644 --- a/assets/data/characters/bf-car.xml +++ b/assets/data/characters/bf-car.xml @@ -1,12 +1,18 @@ - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/characters/da.xml b/assets/data/characters/da.xml index 2ef057ba..f176be02 100644 --- a/assets/data/characters/da.xml +++ b/assets/data/characters/da.xml @@ -1,8 +1,13 @@ - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/characters/dad.xml b/assets/data/characters/dad.xml index 1b1e0f04..39319727 100644 --- a/assets/data/characters/dad.xml +++ b/assets/data/characters/dad.xml @@ -1,8 +1,13 @@ - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/characters/mom-car.xml b/assets/data/characters/mom-car.xml index fa7c18dd..e511498f 100644 --- a/assets/data/characters/mom-car.xml +++ b/assets/data/characters/mom-car.xml @@ -1,10 +1,16 @@ - - - - + + + + + + + + + + \ No newline at end of file diff --git a/assets/data/characters/mom.xml b/assets/data/characters/mom.xml index 4781ede4..43d52076 100644 --- a/assets/data/characters/mom.xml +++ b/assets/data/characters/mom.xml @@ -1,10 +1,16 @@ - - - - + + + + - + + + + + + + \ No newline at end of file diff --git a/assets/data/config/credits.xml b/assets/data/config/credits.xml index 56a5cafd..4f300fcb 100644 --- a/assets/data/config/credits.xml +++ b/assets/data/config/credits.xml @@ -3,5 +3,5 @@ - + diff --git a/assets/data/config/menuItems.txt b/assets/data/config/menuItems.txt index 6fc72f13..a8ec55b1 100644 --- a/assets/data/config/menuItems.txt +++ b/assets/data/config/menuItems.txt @@ -1,4 +1,4 @@ story mode freeplay options -donate \ No newline at end of file +credits \ No newline at end of file diff --git a/assets/data/scripts/pixel.hx b/assets/data/scripts/pixel.hx index cc8239cf..8f1887c8 100644 --- a/assets/data/scripts/pixel.hx +++ b/assets/data/scripts/pixel.hx @@ -1,3 +1,4 @@ +// import funkin.game.HudCamera; import funkin.backend.scripting.events.NoteHitEvent; @@ -162,16 +163,18 @@ function postUpdate(elapsed) { if (enableCameraHacks) { for(p in strumLines) p.notes.forEach(function(n) { + if(n.isSustainNote) return; // hacky fix for hold n.y -= n.y % daPixelZoom; n.x -= n.x % daPixelZoom; }); } + var zoom = 1 / daPixelZoom / Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y); for(e in pixellyCameras) { if (!e.exists) continue; - e.zoom = 1 / daPixelZoom / Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y); + e.zoom = zoom; } for(e in pixellyShaders) { - e.pixelZoom = 1 / daPixelZoom / Math.min(FlxG.scaleMode.scale.x, FlxG.scaleMode.scale.y); + e.pixelZoom = zoom; } } \ No newline at end of file diff --git a/assets/data/scripts/week6-pause.hx b/assets/data/scripts/week6-pause.hx index b382af35..6a4455cd 100644 --- a/assets/data/scripts/week6-pause.hx +++ b/assets/data/scripts/week6-pause.hx @@ -73,7 +73,8 @@ function confText(text) { } function destroy() { - FlxG.cameras.remove(pauseCam); + if(FlxG.cameras.list.contains(pauseCam)) + FlxG.cameras.remove(pauseCam); } var canDoShit = true; diff --git a/assets/data/stages/limo.hx b/assets/data/stages/limo.hx index 0116d55a..d9a58d3c 100644 --- a/assets/data/stages/limo.hx +++ b/assets/data/stages/limo.hx @@ -1,39 +1,43 @@ var fastCarCanDrive:Bool = true; -function create() { +function create() resetFastCar(); -} -function update(elapsed:Float) { + +function update(elapsed:Float) +{ if (!fastCarCanDrive && fastCar.x > 2000) { resetFastCar(); fastCarCanDrive = true; } + var offset = Math.sin(curBeatFloat / 4) * Math.cos(curBeatFloat / 16) * 85; + // back limo drive if (Options.lowMemoryMode) { - bgLimo.frameOffset.x = Math.sin(curBeatFloat / 4) * Math.cos(curBeatFloat / 16) * 85; + bgLimo.frameOffset.x = offset; } else { for(e in [bgLimo, dancer1, dancer2, dancer3, dancer4, dancer5]) { - e.frameOffset.x = Math.sin(curBeatFloat / 4) * Math.cos(curBeatFloat / 16) * 85; + e.frameOffset.x = offset; } } } -function beatHit(curBeat:Int) { +function beatHit(curBeat:Int) if (FlxG.random.bool(10) && fastCarCanDrive) fastCarDrive(); -} - -function resetFastCar() { +function resetFastCar() +{ fastCar.x = -12600; fastCar.y = FlxG.random.int(140, 250); fastCar.velocity.x = 0; + fastCar.moves = false; fastCarCanDrive = true; } function fastCarDrive() { FlxG.sound.play(Paths.soundRandom('carPass', 0, 1), 0.7); - fastCar.velocity.x = (FlxG.random.int(170, 220) / FlxG.elapsed) * 3; + fastCar.velocity.x = FlxG.random.int(170, 220) * 60 * 3; + fastCar.moves = true; fastCarCanDrive = false; } \ No newline at end of file diff --git a/assets/data/stages/philly.hx b/assets/data/stages/philly.hx index 989b29b5..6b120b51 100644 --- a/assets/data/stages/philly.hx +++ b/assets/data/stages/philly.hx @@ -1,3 +1,4 @@ +// import funkin.game.PlayState; // phillyTrain is automatically added by Stage.hx diff --git a/assets/data/stages/school-evil.hx b/assets/data/stages/school-evil.hx index af086dab..c29dbeea 100644 --- a/assets/data/stages/school-evil.hx +++ b/assets/data/stages/school-evil.hx @@ -1,5 +1,4 @@ function create() { - importScript("data/scripts/pixel"); isSpooky = true; // Make transition work between school types diff --git a/assets/data/stages/school-evil.xml b/assets/data/stages/school-evil.xml index 568b3aad..48ee14b7 100644 --- a/assets/data/stages/school-evil.xml +++ b/assets/data/stages/school-evil.xml @@ -1,5 +1,6 @@ + \ No newline at end of file diff --git a/assets/data/stages/school.hx b/assets/data/stages/school.hx index 5cba9421..dc55a69d 100644 --- a/assets/data/stages/school.hx +++ b/assets/data/stages/school.hx @@ -1,5 +1,5 @@ +// function create() { - importScript("data/scripts/pixel"); if (PlayState.SONG.meta.name.toLowerCase() == "roses") { bgGirls.animation.remove("danceLeft"); bgGirls.animation.remove("danceRight"); diff --git a/assets/data/stages/school.xml b/assets/data/stages/school.xml index dabf463b..7a2740ad 100644 --- a/assets/data/stages/school.xml +++ b/assets/data/stages/school.xml @@ -1,5 +1,7 @@ + + diff --git a/assets/images/characters/mom.xml b/assets/images/characters/mom.xml index 67eeb6b7..51132b97 100644 --- a/assets/images/characters/mom.xml +++ b/assets/images/characters/mom.xml @@ -15,8 +15,9 @@ - - + + diff --git a/assets/images/editors/icons.png b/assets/images/editors/icons.png index 41dee63c..cccc10f9 100644 Binary files a/assets/images/editors/icons.png and b/assets/images/editors/icons.png differ diff --git a/assets/images/menus/mainmenu/credits.png b/assets/images/menus/mainmenu/credits.png new file mode 100644 index 00000000..cb1d9863 Binary files /dev/null and b/assets/images/menus/mainmenu/credits.png differ diff --git a/assets/images/menus/mainmenu/credits.xml b/assets/images/menus/mainmenu/credits.xml new file mode 100644 index 00000000..c3c2385e --- /dev/null +++ b/assets/images/menus/mainmenu/credits.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/menus/mainmenu/donate.png b/assets/images/menus/mainmenu/donate.png index e4a42749..cf6a6d11 100644 Binary files a/assets/images/menus/mainmenu/donate.png and b/assets/images/menus/mainmenu/donate.png differ diff --git a/assets/images/menus/mainmenu/freeplay.png b/assets/images/menus/mainmenu/freeplay.png index a34eed2e..361ddfa7 100644 Binary files a/assets/images/menus/mainmenu/freeplay.png and b/assets/images/menus/mainmenu/freeplay.png differ diff --git a/assets/images/menus/mainmenu/freeplay.xml b/assets/images/menus/mainmenu/freeplay.xml index 4c288a07..f66f748f 100644 --- a/assets/images/menus/mainmenu/freeplay.xml +++ b/assets/images/menus/mainmenu/freeplay.xml @@ -1,17 +1,18 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/images/menus/mainmenu/options.png b/assets/images/menus/mainmenu/options.png index ebe1f86f..20688ecc 100644 Binary files a/assets/images/menus/mainmenu/options.png and b/assets/images/menus/mainmenu/options.png differ diff --git a/assets/images/menus/mainmenu/story mode.png b/assets/images/menus/mainmenu/story mode.png index 162f403d..466fa338 100644 Binary files a/assets/images/menus/mainmenu/story mode.png and b/assets/images/menus/mainmenu/story mode.png differ diff --git a/assets/shaders/engine/editorBlur.frag b/assets/shaders/engine/editorBlur.frag index 937aa028..59b91c72 100644 --- a/assets/shaders/engine/editorBlur.frag +++ b/assets/shaders/engine/editorBlur.frag @@ -18,17 +18,17 @@ void main() { return; vec4 color = getColor(camPos); - float fsteps = steps; - for(float inside = 1.0; inside < stepsInside+1.0; inside++) { + float fsteps = float(steps); + for(float inside = 1.0; inside < float(stepsInside)+1.0; inside++) { for(int i = 0; i < steps; i++) { - float fi = i; + float fi = float(i); color += getColor(camPos + vec2( - strength * (inside / stepsInside) * cos(fi / fsteps * (PI * 2.0)), - strength * (inside / stepsInside) * sin(fi / fsteps * (PI * 2.0)) + strength * (inside / float(stepsInside)) * cos(fi / fsteps * (PI * 2.0)), + strength * (inside / float(stepsInside)) * sin(fi / fsteps * (PI * 2.0)) )); } } - color /= steps * stepsInside; + color /= vec4(steps * stepsInside); gl_FragColor = color; -} \ No newline at end of file +} diff --git a/assets/videos/guns-cutscene.mp4 b/assets/videos/guns-cutscene.mp4 deleted file mode 100644 index cfc7500a..00000000 Binary files a/assets/videos/guns-cutscene.mp4 and /dev/null differ diff --git a/assets/videos/stress-cutscene.mp4 b/assets/videos/stress-cutscene.mp4 deleted file mode 100644 index 036a54d1..00000000 Binary files a/assets/videos/stress-cutscene.mp4 and /dev/null differ diff --git a/assets/videos/ugh-cutscene.mp4 b/assets/videos/ugh-cutscene.mp4 deleted file mode 100644 index 8f30a8c7..00000000 Binary files a/assets/videos/ugh-cutscene.mp4 and /dev/null differ diff --git a/commandline/Main.hx b/commandline/Main.hx index 8c2f9301..1b5b01b8 100644 --- a/commandline/Main.hx +++ b/commandline/Main.hx @@ -12,7 +12,7 @@ class Main { { names: ["setup"], doc: "Setups (or updates) all libraries required for the engine.", - func: Update.main, + func: Setup.main, dDoc: [ "Usage: setup", "", diff --git a/commandline/commands/Update.hx b/commandline/commands/Setup.hx similarity index 97% rename from commandline/commands/Update.hx rename to commandline/commands/Setup.hx index 2513f747..7671c6f4 100644 --- a/commandline/commands/Update.hx +++ b/commandline/commands/Setup.hx @@ -6,7 +6,7 @@ import sys.io.File; import sys.io.Process; import sys.FileSystem; -class Update { +class Setup { private static function recursiveDelete(path:String) { for(file in FileSystem.readDirectory(path)) { var p = '$path/$file'; @@ -54,10 +54,10 @@ class Update { case "lib" | "git" | "custom": var lib:Library = { name: libNode.att.name, - type: libNode.name + type: libNode.name, + skipDeps: libNode.has.skipDeps ? libNode.att.skipDeps == "true" : false, }; if (libNode.has.global) lib.global = libNode.att.global; - if (libNode.has.skipDeps) lib.skipDeps = libNode.att.skipDeps; switch (lib.type) { case "lib": if (libNode.has.version) lib.version = libNode.att.version; @@ -139,7 +139,7 @@ class Update { case INSTALL: var lib:Library = event.data; var globalSuffix:Null = lib.global == "true" ? " --global" : ""; - var skipDeps = lib.skipDeps == "true" ? " --skip-dependencies" : ""; + var skipDeps = lib.skipDeps ? " --skip-dependencies" : ""; var commandPrefix = commandSuffix + globalSuffix + skipDeps;// + " --no-timeout"; switch(lib.type) { case "lib": @@ -278,8 +278,8 @@ class Update { typedef Library = { var name:String; var type:String; + var skipDeps:Bool; var ?global:String; - var ?skipDeps:String; var ?recursive:String; var ?version:String; var ?ref:String; diff --git a/project.xml b/project.xml index 017d8ba8..fc5f5474 100644 --- a/project.xml +++ b/project.xml @@ -153,6 +153,8 @@ + + @@ -201,6 +203,7 @@ + diff --git a/source/funkin/backend/FlxAnimate.hx b/source/funkin/backend/FlxAnimate.hx index c2b6e8c6..e22cb867 100644 --- a/source/funkin/backend/FlxAnimate.hx +++ b/source/funkin/backend/FlxAnimate.hx @@ -15,7 +15,7 @@ class FlxAnimate extends flxanimate.FlxAnimate { for (camera in cameras) { rMatrix.identity(); - rMatrix.translate(-limb.offset.x, -limb.offset.y); + limb.prepareMatrix(rMatrix, FlxFrameAngle.ANGLE_0, _checkFlipX() != camera.flipX, _checkFlipY() != camera.flipY); rMatrix.concat(_rMatrix); if (!camera.visible || !camera.exists || !limbOnScreen(limb, _rMatrix, camera)) return; diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index 4a42326d..09cf8f7b 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -96,6 +96,13 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I super.update(elapsed); if (animateAtlas != null) animateAtlas.update(elapsed); + + // hate how it looks like but hey at least its optimized and fast - Nex + if (!debugMode && isAnimFinished()) { + var name = getAnimName() + '-loop'; + if (hasAnimation(name)) + playAnim(name, null, lastAnimContext); + } } public function loadSprite(path:String, Unique:Bool = false, Key:String = null) @@ -287,11 +294,16 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I #if REGION public var lastAnimContext:PlayAnimContext = DANCE; - public function playAnim(AnimName:String, Force:Bool = false, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0):Void + public function playAnim(AnimName:String, ?Force:Null, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0):Void { if (AnimName == null) return; + if (Force == null) { + var anim = animDatas.get(AnimName); + Force = anim != null && anim.forced; + } + if (animateAtlas != null) { @:privateAccess @@ -328,6 +340,10 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I return (animateAtlas != null) ? atlasPlayingAnim : animation.name; } + public inline function isAnimReversed():Bool { + return animateAtlas != null ? animateAtlas.anim.reversed : animation.curAnim != null ? animation.curAnim.reversed : false; + } + public inline function removeAnimation(name:String) { if (animateAtlas != null) @:privateAccess animateAtlas.anim.animsMap.remove(name); diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index eff3fd8e..64b8ce93 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -69,7 +69,11 @@ class EventsData { hscriptInterp.variables.set("String", TString); hscriptInterp.variables.set("StrumLine", TStrumLine); hscriptInterp.variables.set("ColorWheel", TColorWheel); - hscriptInterp.variables.set("DropDown", Reflect.makeVarArgs((args) -> {return args.length > 0 ? TDropDown([for (arg in args) Std.string(arg)]) : TDropDown(["null"]);})); + hscriptInterp.variables.set("DropDown", Reflect.makeVarArgs(function(args:Array):EventParamType { + var flatArgs = CoolUtil.deepFlatten(args); + if(flatArgs.length == 0) return TDropDown(["null"]); + return TDropDown([for (arg in flatArgs) Std.string(arg)]); + })); hscriptInterp.variables.set("Character", TCharacter); hscriptInterp.variables.set("Stage", TStage); @@ -77,11 +81,12 @@ class EventsData { hscriptParser.allowJSON = hscriptParser.allowMetadata = false; for (file in Paths.getFolderContent('data/events/', true, BOTH)) { - if (Path.extension(file) != "json" && Path.extension(file) != "pack") continue; - var eventName:String = Path.withoutExtension(Path.withoutDirectory(file)); + var ext = Path.extension(file); + if (ext != "json" && ext != "pack") continue; + var eventName:String = CoolUtil.getFilename(file); var fileTxt:String = Assets.getText(file); - if (Path.extension(file) == "pack") { + if (ext == "pack") { var arr = fileTxt.split("________PACKSEP________"); eventName = Path.withoutExtension(arr[0]); fileTxt = arr[2]; @@ -93,13 +98,17 @@ class EventsData { eventsParams.set(eventName, []); try { - var data:Dynamic = Json.parse(fileTxt); + var data:EventInfoFile = cast Json.parse(fileTxt); if (data == null || data.params == null) continue; var finalParams:Array = []; - for (paramData in cast(data.params, Array)) { + for (paramData in data.params) { try { - finalParams.push({name: paramData.name, type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)), defValue: paramData.defaultValue}); + finalParams.push({ + name: paramData.name, + type: hscriptInterp.expr(hscriptParser.parseString(paramData.type)), + defValue: paramData.defaultValue + }); } catch (e) {trace('Error parsing event param ${paramData.name} - ${eventName}: $e'); finalParams.push(null);} } eventsParams.set(eventName, finalParams); @@ -110,6 +119,14 @@ class EventsData { } } +typedef EventInfoFile = { + var params:Array<{ + var name:String; + var type:String; + var defaultValue:Dynamic; + }>; +} + typedef EventInfo = { var params:Array; var paramValues:Array; diff --git a/source/funkin/backend/scripting/EventManager.hx b/source/funkin/backend/scripting/EventManager.hx index 84e7d33f..ab47bb5d 100644 --- a/source/funkin/backend/scripting/EventManager.hx +++ b/source/funkin/backend/scripting/EventManager.hx @@ -14,8 +14,9 @@ class EventManager { var index = eventKeys.indexOf(c); if (index < 0) { eventKeys.push(c); - eventValues.push(Type.createInstance(c, [])); - return cast eventValues.last(); + var ret; + eventValues.push(ret = Type.createInstance(c, [])); + return cast ret; } return cast eventValues[index]; diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index f2638f82..c19c82fc 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -64,7 +64,7 @@ class HScript extends Script { } private function importFailedCallback(cl:Array):Bool { - if(_importFailedCallback(cl, "") || _importFailedCallback(cl, "source/")) { + if(_importFailedCallback(cl, "source/") || _importFailedCallback(cl, "")) { return true; } return false; diff --git a/source/funkin/backend/scripting/ModState.hx b/source/funkin/backend/scripting/ModState.hx index adc0a42c..94a6ac7b 100644 --- a/source/funkin/backend/scripting/ModState.hx +++ b/source/funkin/backend/scripting/ModState.hx @@ -1,10 +1,38 @@ package funkin.backend.scripting; class ModState extends MusicBeatState { + + /** + * Name of HScript file in assets/data/states. + */ public static var lastName:String = null; - public function new(stateName:String) { - if (stateName != null) - lastName = stateName; + /** + * Last Optional extra data. + */ + public static var lastData:Dynamic = null; + + /** + * Optional extra data. + */ + public var data:Dynamic = null; + + /** + * ModState Constructor. + * Inherits from MusicBeatState and allows the execution of an HScript from assets/data/states passed via parameters. + * + * @param _stateName Name or path to a HScript file from assets/data/states. + * @param _data Optional extra Dynamic data passed from a previous state (JSON suggested). + */ + public function new(_stateName:String, ?_data:Dynamic) { + if(_stateName != null && _stateName != lastName) { + lastName = _stateName; + lastData = null; + } + + if(_data != null) + lastData = _data; + + data = lastData; super(true, lastName); } } \ No newline at end of file diff --git a/source/funkin/backend/scripting/ModSubState.hx b/source/funkin/backend/scripting/ModSubState.hx index 5f06072c..acd90f62 100644 --- a/source/funkin/backend/scripting/ModSubState.hx +++ b/source/funkin/backend/scripting/ModSubState.hx @@ -1,10 +1,38 @@ package funkin.backend.scripting; class ModSubState extends MusicBeatSubstate { + + /** + * Name of HScript file in assets/data/states. + */ public static var lastName:String = null; - public function new(stateName:String) { - if (stateName != null) - lastName = stateName; + /** + * Last Optional extra data. + */ + public static var lastData:Dynamic = null; + + /** + * Optional extra data. + */ + public var data:Dynamic = null; + + /** + * ModSubState Constructor. + * Inherits from MusicBeatSubstate and allows the execution of an HScript from assets/data/states passed via parameters. + * + * @param _stateName Name or path to a HScript file from assets/data/states. + * @param _data Optional extra Dynamic data passed from a previous state (JSON suggested). + */ + public function new(_stateName:String, ?_data:Dynamic) { + if(_stateName != null && _stateName != lastName) { + lastName = _stateName; + lastData = null; + } + + if(_data != null) + lastData = _data; + + data = lastData; super(true, lastName); } } \ No newline at end of file diff --git a/source/funkin/backend/scripting/Script.hx b/source/funkin/backend/scripting/Script.hx index b70f5766..d9aaea5a 100644 --- a/source/funkin/backend/scripting/Script.hx +++ b/source/funkin/backend/scripting/Script.hx @@ -288,7 +288,7 @@ class Script extends FlxBasic implements IFlxDestroyable { public function get(variable:String):Dynamic {return null;} /** - * Gets the variable `variable` from the script's variables. + * Sets the variable `variable` from the script's variables. * @param variable Name of the variable. * @return Variable (or null if it doesn't exists) */ diff --git a/source/funkin/backend/scripting/events/DirectionAnimEvent.hx b/source/funkin/backend/scripting/events/DirectionAnimEvent.hx index a23626f4..dce2eaff 100644 --- a/source/funkin/backend/scripting/events/DirectionAnimEvent.hx +++ b/source/funkin/backend/scripting/events/DirectionAnimEvent.hx @@ -26,7 +26,7 @@ final class DirectionAnimEvent extends CancellableEvent { **/ public var frame:Int; /** - Force the animation to replay even if it's already playing. + Force the animation to replay even if it's already playing (if it's null it will be forced based on the sprite's data xml, if it has one). **/ - public var force:Bool = true; + public var force:Null; } diff --git a/source/funkin/backend/scripting/events/NoteHitEvent.hx b/source/funkin/backend/scripting/events/NoteHitEvent.hx index d5f64dc3..4bac078e 100644 --- a/source/funkin/backend/scripting/events/NoteHitEvent.hx +++ b/source/funkin/backend/scripting/events/NoteHitEvent.hx @@ -100,9 +100,9 @@ final class NoteHitEvent extends CancellableEvent { */ public var ratingAntialiasing:Bool = true; /** - * Whenever the animation should be forced to play. + * Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one). */ - public var forceAnim:Bool = true; + public var forceAnim:Null = true; /** * Prevents the default sing animation from being played. diff --git a/source/funkin/backend/scripting/events/NoteMissEvent.hx b/source/funkin/backend/scripting/events/NoteMissEvent.hx index e9475bb0..72f87ddd 100644 --- a/source/funkin/backend/scripting/events/NoteMissEvent.hx +++ b/source/funkin/backend/scripting/events/NoteMissEvent.hx @@ -29,9 +29,9 @@ final class NoteMissEvent extends CancellableEvent { public var gfSadAnim:String; public var forceGfAnim:Bool; /** - * Whenever the animation should be forced to play. + * Whenever the animation should be forced to play (if it's null it will be forced based on the sprite's data xml, if it has one). */ - public var forceAnim:Bool; + public var forceAnim:Null; /** * Suffix of the animation. "miss" for miss notes, "-alt" for alt notes, "" for normal ones. */ diff --git a/source/funkin/backend/scripting/events/PlayAnimEvent.hx b/source/funkin/backend/scripting/events/PlayAnimEvent.hx index a555c3ed..f51c8243 100644 --- a/source/funkin/backend/scripting/events/PlayAnimEvent.hx +++ b/source/funkin/backend/scripting/events/PlayAnimEvent.hx @@ -7,9 +7,9 @@ final class PlayAnimEvent extends CancellableEvent { public var animName:String; /** - Whenever the animation will be forced or not. + Whenever the animation will be forced or not (if it's null it will be forced based on the sprite's data xml, if it has one). **/ - public var force:Bool; + public var force:Null; /** Whenever the animation will play in reverse or not diff --git a/source/funkin/backend/scripting/events/SplashShowEvent.hx b/source/funkin/backend/scripting/events/SplashShowEvent.hx new file mode 100644 index 00000000..46504d44 --- /dev/null +++ b/source/funkin/backend/scripting/events/SplashShowEvent.hx @@ -0,0 +1,23 @@ +package funkin.backend.scripting.events; + +import funkin.game.SplashGroup; +import funkin.game.Strum; + +final class SplashShowEvent extends CancellableEvent { + /** + Splash name/type + **/ + public var splashName:String; + /** + Splash that is shown + **/ + public var splash:FunkinSprite; + /** + Strum that the splash is shown on + **/ + public var strum:Strum; + /** + Splash group that the splash is from + **/ + public var group:SplashGroup; +} \ No newline at end of file diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index b676ae45..0b282ffb 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -438,34 +438,33 @@ uniform sampler2D bitmap; uniform bool hasTransform; uniform bool hasColorTransform; -vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) -{ - vec4 color = texture2D(bitmap, coord); - if (!hasTransform) - { +vec4 applyFlixelEffects(vec4 color) { + if(!hasTransform) { return color; } - if (color.a == 0.0) - { + if(color.a == 0.0) { return vec4(0.0, 0.0, 0.0, 0.0); } - if (!hasColorTransform) - { + if(!hasColorTransform) { return color * openfl_Alphav; } - color = vec4(color.rgb / color.a, color.a); + color.rgb = color.rgb / color.a; color = clamp(openfl_ColorOffsetv + (color * openfl_ColorMultiplierv), 0.0, 1.0); - if (color.a > 0.0) - { + if(color.a > 0.0) { return vec4(color.rgb * color.a * openfl_Alphav, color.a * openfl_Alphav); } return vec4(0.0, 0.0, 0.0, 0.0); } +vec4 flixel_texture2D(sampler2D bitmap, vec2 coord) { + vec4 color = texture2D(bitmap, coord); + return applyFlixelEffects(color); +} + uniform vec4 _camSize; float map(float value, float min1, float max1, float min2, float max2) { @@ -484,9 +483,7 @@ vec4 textureCam(sampler2D bitmap, vec2 pos) { return flixel_texture2D(bitmap, camToOg(pos)); }"; - public static final fragBody:String = "vec4 color = flixel_texture2D (bitmap, openfl_TextureCoordv); - gl_FragColor = color; -"; + public static final fragBody:String = "gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv);"; public static final vertHeader:String = "attribute float openfl_Alpha; attribute vec4 openfl_ColorMultiplier; attribute vec4 openfl_ColorOffset; @@ -500,16 +497,26 @@ varying vec2 openfl_TextureCoordv; uniform mat4 openfl_Matrix; uniform bool openfl_HasColorTransform; -uniform vec2 openfl_TextureSize;"; +uniform vec2 openfl_TextureSize; + +attribute float alpha; +attribute vec4 colorMultiplier; +attribute vec4 colorOffset; +uniform bool hasColorTransform;"; public static final vertBody:String = "openfl_Alphav = openfl_Alpha; openfl_TextureCoordv = openfl_TextureCoord; -if (openfl_HasColorTransform) { - +if(openfl_HasColorTransform) { openfl_ColorMultiplierv = openfl_ColorMultiplier; openfl_ColorOffsetv = openfl_ColorOffset / 255.0; +} +openfl_Alphav = openfl_Alpha * alpha; + +if(hasColorTransform) { + openfl_ColorOffsetv = colorOffset / 255.0; + openfl_ColorMultiplierv = colorMultiplier; } gl_Position = openfl_Matrix * openfl_Position;"; @@ -517,31 +524,14 @@ gl_Position = openfl_Matrix * openfl_Position;"; public static final defaultVertexSource:String = "#pragma header -attribute float alpha; -attribute vec4 colorMultiplier; -attribute vec4 colorOffset; -uniform bool hasColorTransform; - -void main(void) -{ +void main(void) { #pragma body - - openfl_Alphav = openfl_Alpha * alpha; - - if (hasColorTransform) - { - openfl_ColorOffsetv = colorOffset / 255.0; - openfl_ColorMultiplierv = colorMultiplier; - } }"; - - // TODO: camera stuff public static final defaultFragmentSource:String = "#pragma header -void main(void) -{ - gl_FragColor = flixel_texture2D(bitmap, openfl_TextureCoordv); +void main(void) { + #pragma body }"; } diff --git a/source/funkin/backend/system/Flags.hx b/source/funkin/backend/system/Flags.hx new file mode 100644 index 00000000..fed62113 --- /dev/null +++ b/source/funkin/backend/system/Flags.hx @@ -0,0 +1,63 @@ +package funkin.backend.system; + +import lime.utils.AssetLibrary; +import lime.utils.AssetType; + +import funkin.backend.assets.IModsAssetLibrary; +import funkin.backend.assets.ScriptedAssetLibrary; + +/** + * A class that reads the `flags.ini` file, allowing to read settable Flags (customs too). + */ +@:build(funkin.backend.system.macros.FlagMacro.build()) +class Flags { + public static var SONGS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override"; + public static var WEEKS_LIST_MOD_MODE:Allow<"prepend", "override", "append"> = "override"; + + // Internal stuff + + @:bypass public static var customFlags:Map = []; + + public static function load() { + var flags:Map = []; + + final flagsPath = Paths.getPath("flags.ini"); + + for(lib in Paths.assetsTree.libraries) { + if(lib.exists(flagsPath, AssetType.TEXT)) { + var data:String = lib.getAsset(flagsPath, AssetType.TEXT); + var trimmed:String; + var splitContent = [for(e in data.split("\n")) if ((trimmed = e.trim()) != "") trimmed]; + + for(line in splitContent) { + if (line.startsWith(";")) continue; + if (line.startsWith("#")) continue; + if (line.startsWith("//")) continue; + if(line.length == 0) continue; + if(line.charAt(0) == "[" && line.charAt(line.length-1) == "]") continue; + + var index = line.indexOf("="); + if(index == -1) continue; + var name = line.substr(0, index).trim(); + var value = line.substr(index+1).trim(); + + if (value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code) + value = value.substr(1, value.length - 2); + + if (value.length == 0 || name.length == 0) + continue; + + if(!flags.exists(name)) + flags[name] = value; + } + } + } + + customFlags = []; + reset(); + for(name=>value in flags) + if(!parse(name, value)) { + customFlags.set(name, value); + } + } +} \ No newline at end of file diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index 17b3587f..3d2358c2 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -8,6 +8,7 @@ import funkin.backend.assets.ModsFolder; import funkin.backend.chart.EventsData; import funkin.menus.BetaWarningState; import funkin.menus.TitleState; +import funkin.backend.system.framerate.Framerate; /** * Simple state used for loading the game @@ -57,10 +58,14 @@ class MainState extends FlxState { ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder); TranslationUtil.findAllLanguages(); TranslationUtil.setLanguage(); + Flags.load(); DiscordUtil.init(); EventsData.reloadEvents(); TitleState.initialized = false; + if(Framerate.isLoaded) + Framerate.instance.reload(); + if (betaWarningShown) FlxG.switchState(new TitleState()); else { diff --git a/source/funkin/backend/system/framerate/CodenameBuildField.hx b/source/funkin/backend/system/framerate/CodenameBuildField.hx index 3175a01e..5519f4d8 100644 --- a/source/funkin/backend/system/framerate/CodenameBuildField.hx +++ b/source/funkin/backend/system/framerate/CodenameBuildField.hx @@ -9,6 +9,9 @@ class CodenameBuildField extends TextField { defaultTextFormat = Framerate.textFormat; autoSize = LEFT; multiline = wordWrap = false; - text = 'Codename Engine ${Main.releaseCycle}\nCommit ${GitCommitMacro.commitNumber} (${GitCommitMacro.commitHash})'; + reload(); } + + public function reload() + text = 'Codename Engine ${Main.releaseCycle}\nCommit ${GitCommitMacro.commitNumber} (${GitCommitMacro.commitHash})'; } diff --git a/source/funkin/backend/system/framerate/Framerate.hx b/source/funkin/backend/system/framerate/Framerate.hx index 8faa09ee..7e8e3f8e 100644 --- a/source/funkin/backend/system/framerate/Framerate.hx +++ b/source/funkin/backend/system/framerate/Framerate.hx @@ -82,6 +82,16 @@ class Framerate extends Sprite { #end } + public function reload() { + for(c in categories) + c.reload(); + #if SHOW_BUILD_ON_FPS + codenameBuildField.reload(); + #end + memoryCounter.reload(); + fpsCounter.reload(); + } + private function __addCategory(category:FramerateCategory) { categories.push(category); __addToList(category); @@ -115,9 +125,16 @@ class Framerate extends Sprite { bgSprite.scaleX = width; bgSprite.scaleY = height; - var y:Float = height + 4; + var selectable = debugMode == 2; + { // idk i tried to make it more lookable:sob: - Nex + memoryCounter.memoryText.selectable = memoryCounter.memoryPeakText.selectable = + fpsCounter.fpsNum.selectable = fpsCounter.fpsLabel.selectable = + #if SHOW_BUILD_ON_FPS codenameBuildField.selectable = #end selectable; + } + var y:Float = height + 4; for(c in categories) { + c.title.selectable = c.text.selectable = selectable; c.alpha = debugAlpha; c.x = FlxMath.lerp(-c.width - offset.x, 0, debugAlpha); c.y = y; diff --git a/source/funkin/backend/system/framerate/FramerateCategory.hx b/source/funkin/backend/system/framerate/FramerateCategory.hx index f1125eb4..db89137f 100644 --- a/source/funkin/backend/system/framerate/FramerateCategory.hx +++ b/source/funkin/backend/system/framerate/FramerateCategory.hx @@ -29,6 +29,7 @@ class FramerateCategory extends Sprite { label.x = 0; label.y = 0; label.defaultTextFormat = new TextFormat(Framerate.fontName, label == this.title ? 18 : 12, -1); + label.selectable = false; addChild(label); } this.title.text = title; @@ -39,6 +40,8 @@ class FramerateCategory extends Sprite { this.text.y = this.title.y + this.title.height + 2; } + public function reload() {} + public override function __enterFrame(t:Int) { if (alpha <= 0.05) return; super.__enterFrame(t); @@ -49,4 +52,4 @@ class FramerateCategory extends Sprite { bgSprite.scaleX = width; bgSprite.scaleY = height; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/system/framerate/FramerateCounter.hx b/source/funkin/backend/system/framerate/FramerateCounter.hx index e07a2e02..d6ff4b67 100644 --- a/source/funkin/backend/system/framerate/FramerateCounter.hx +++ b/source/funkin/backend/system/framerate/FramerateCounter.hx @@ -1,28 +1,17 @@ package funkin.backend.system.framerate; -import haxe.ds.Vector; import openfl.display.Sprite; import openfl.text.TextField; import openfl.text.TextFormat; -inline final FRAME_TIME_HISTORY = 20; - class FramerateCounter extends Sprite { public var fpsNum:TextField; public var fpsLabel:TextField; - - public var fpsHistoryIndex:Int = 0; - public var fpsHistory:Vector; + public var lastFPS:Float = 0; public function new() { super(); - fpsHistory = new Vector(FRAME_TIME_HISTORY); - // Initialize to 60 FPS, so that the initial estimation until we get enough data is always reasonable. - for(i in 0...FRAME_TIME_HISTORY) { - fpsHistory[i] = 1000.0 / 60.0; - } - fpsNum = new TextField(); fpsLabel = new TextField(); @@ -33,29 +22,20 @@ class FramerateCounter extends Sprite { label.text = "FPS"; label.multiline = label.wordWrap = false; label.defaultTextFormat = new TextFormat(Framerate.fontName, label == fpsNum ? 18 : 12, -1); + label.selectable = false; addChild(label); } } + public function reload() {} + public override function __enterFrame(t:Int) { if (alpha <= 0.05) return; super.__enterFrame(t); - // todo use gpu time - fpsHistory[fpsHistoryIndex] = FlxG.elapsed * 1000; - fpsHistoryIndex = (fpsHistoryIndex + 1) % FRAME_TIME_HISTORY; - - // Calculate average CPU time. - // Code based on Godot's FPS counter. - var cpuTime = 0.0; - for(i in 0...FRAME_TIME_HISTORY) { - cpuTime += fpsHistory[i]; - } - cpuTime /= FRAME_TIME_HISTORY; - cpuTime = Math.max(0.01, cpuTime); // Prevent unrealistically low values. - - fpsNum.text = Std.string(Math.floor(1000.0 / cpuTime)); + lastFPS = CoolUtil.fpsLerp(lastFPS, FlxG.elapsed == 0 ? 0 : (1 / FlxG.elapsed), 0.25); + fpsNum.text = Std.string(Math.floor(lastFPS)); fpsLabel.x = fpsNum.x + fpsNum.width; fpsLabel.y = (fpsNum.y + fpsNum.height) - fpsLabel.height; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/system/framerate/MemoryCounter.hx b/source/funkin/backend/system/framerate/MemoryCounter.hx index 33efff59..1c26a27d 100644 --- a/source/funkin/backend/system/framerate/MemoryCounter.hx +++ b/source/funkin/backend/system/framerate/MemoryCounter.hx @@ -24,11 +24,14 @@ class MemoryCounter extends Sprite { label.text = "FPS"; label.multiline = label.wordWrap = false; label.defaultTextFormat = new TextFormat(Framerate.fontName, 12, -1); + label.selectable = false; addChild(label); } memoryPeakText.alpha = 0.5; } + public function reload() {} + public override function __enterFrame(t:Int) { if (alpha <= 0.05) return; super.__enterFrame(t); @@ -40,4 +43,4 @@ class MemoryCounter extends Sprite { memoryPeakText.x = memoryText.x + memoryText.width; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/system/macros/EventMacro.hx b/source/funkin/backend/system/macros/EventMacro.hx index 810ff1fd..7219a528 100644 --- a/source/funkin/backend/system/macros/EventMacro.hx +++ b/source/funkin/backend/system/macros/EventMacro.hx @@ -75,34 +75,13 @@ class EventMacro { // add a "set this" expr for each variable for(v in values) { var name = v.name; - exprs.push({ - pos: Context.currentPos(), - expr: EBinop(OpAssign, { - pos: Context.currentPos(), - expr: EField({ - pos: Context.currentPos(), - expr: EConst(CIdent("this")) - }, name) - }, { - pos: Context.currentPos(), - expr: EConst(CIdent(name)) - }) - }); + exprs.push(macro this.$name = $i{name}); } // add a "set this" expr to reset each private/hidden variables for(v in hiddenValues) { var name = v.name; - exprs.push({ - pos: Context.currentPos(), - expr: EBinop(OpAssign, { - pos: Context.currentPos(), - expr: EField({ - pos: Context.currentPos(), - expr: EConst(CIdent("this")) - }, name) - }, v.expr) - }); + exprs.push(macro this.$name = ${v.expr}); } exprs.push(macro return this); diff --git a/source/funkin/backend/system/macros/FlagMacro.macro.hx b/source/funkin/backend/system/macros/FlagMacro.macro.hx new file mode 100644 index 00000000..477512c0 --- /dev/null +++ b/source/funkin/backend/system/macros/FlagMacro.macro.hx @@ -0,0 +1,211 @@ +package funkin.backend.system.macros; + +import haxe.macro.Context; +import haxe.macro.Expr; + +using StringTools; + +using haxe.macro.Tools; + +class FlagMacro { + public static function build():Array { + var fields = Context.getBuildFields(); + + var clRef = Context.getLocalClass(); + if (clRef == null) + return fields; + + var cl = clRef.get(); + + var resetExprs:Array = []; + var parserExprs:Array = []; + + for (field in fields) { + var skip = false; + for(meta in field.meta) { + if(meta.name == ":bypass") { + skip = true; + break; + } + } + + if(skip) continue; + + switch (field.kind) { + case FVar(type, expr): + if(expr == null) + Context.error('Flag ' + field.name + ' must have a default value', field.pos); + switch(expr.expr) { + case EConst(CIdent("true")) | EConst(CIdent("false")): + type = macro: Bool; + default: + if(type == null) + Context.error('Flag ' + field.name + ' must have a type', field.pos); + } + + var parser:Expr = null; + var customCheck:Expr = null; + + switch(type) { + case macro: Array: + field.kind = FVar(macro: Array, expr); + parser = macro value.split(",").map((e) -> e.trim()); + case macro: Array: + parser = macro value.split(","); + case macro: Array: + parser = macro value.split(",").map((e) -> { + e = e.trim(); + e == "true" || e == "t" || e == "1"; + }); + case macro: Int: + parser = macro Std.parseInt(value); + case macro: String: + parser = macro value; + case macro: Bool: + parser = macro value == "true" || value == "t" || value == "1"; + case TPath({name: "Allow", pack: [], params: params}): + final NONE = 0; + final STRING = 1; + final INT = 2; + var chosenType = NONE; + + var values:Array = []; + + for(param in params) { + switch(param) { + case TPExpr(e): + switch(e.expr) { + case EConst(CString(s, kind)): + if(chosenType != NONE && chosenType != STRING) + Context.error("Flag " + field.name + " Allow<> can only have one type", field.pos); + chosenType = STRING; + + values.push(s); + case EConst(CInt(num)): + if(chosenType != NONE && chosenType != INT) + Context.error("Flag " + field.name + " Allow<> can only have one type", field.pos); + chosenType = INT; + + values.push(num); + default: + Context.error("Flag " + field.name + " Allow<> unknown type", field.pos); + } + default: + Context.error("Flag " + field.name + " Unknown type when using Allow<>", field.pos); + } + } + + var didFind = false; + for(v in values) { + switch(expr.expr) { + case EConst(CString(s, kind)): + if(v == s) + didFind = true; + case EConst(CInt(num)): + if(v == num) + didFind = true; + default: + } + } + if(!didFind) + Context.error("Flag " + field.name + "'s Allow<> must have a default value that is allowed, " + expr.toString() + " is not allowed", field.pos); + + if(chosenType == NONE) + Context.error("Flag " + field.name + "'s Allow<> must have atleast one value", field.pos); + + var errorMessage = 'Flag ${field.name} must be one of ${values.join(", ")}'; + + var checkExpr = macro value == $v{values.shift()}; + for(v in values) + checkExpr = macro $checkExpr || value == $v{v}; + + if(chosenType == STRING) { + parser = macro value; + field.kind = FVar(macro: String, expr); + } + else if(chosenType == INT) { + parser = macro Std.parseInt(value); + field.kind = FVar(macro: Int, expr); + } else { + field.kind = FVar(macro: Any, expr); + } + + customCheck = macro @:mergeBlock { + if(name == $v{field.name}) { + if($checkExpr) + $i{field.name} = $parser; + else + throw $v{errorMessage}; + return true; + } + } + + case TPath({name: "Array", pack: []}): + Context.error("Flag " + field.name + " cannot be an Array that isnt a String or Bool or TrimmedString", field.pos); + case TPath({name: "Map", pack: []}): + Context.error("Flag " + field.name + " cannot be a Map", field.pos); + default: + Context.error("Flag " + field.name + " must be either a Bool, Int, String, Array, Array or Array", field.pos); + } + + if(parser == null) { + Context.error("Flag " + field.name + " must be either a Bool, Int, String, Array, Array or Array", field.pos); + continue; + } + + resetExprs.push(macro $i{field.name} = ${expr}); + + // parse(name: String, value: String) + + if(customCheck != null) { + parserExprs.push(customCheck); + } else { + parserExprs.push(macro @:mergeBlock { + if(name == $v{field.name}) { + $i{field.name} = $parser; + return true; + } + }); + } + default: + // nothing + } + } + + fields.push({ + name: "reset", + access: [APublic, AStatic], + kind: FFun({ + args: [], + expr: macro $b{resetExprs}, + ret: macro: Void + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + fields.push({ + name: "parse", + access: [APublic, AStatic], + kind: FFun({ + args: [{name: "name", type: macro: String}, {name: "value", type: macro: String}], + expr: macro { + @:mergeBlock $b{parserExprs}; + + return false; + }, + ret: macro: Bool + }), + pos: Context.currentPos(), + doc: null, + meta: [] + }); + + //var printer = new haxe.macro.Printer(); + //trace(printer.printField(fields[fields.length - 2])); + //trace(printer.printField(fields[fields.length - 1])); + + return fields; + } +} \ No newline at end of file diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 437d05c8..fc0a69c5 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -741,7 +741,7 @@ class CoolUtil */ @:noUsing public static inline function openURL(url:String) { #if linux - Sys.command('/usr/bin/xdg-open', [url, "&"]); + Sys.command('/usr/bin/xdg-open', [url]); #else FlxG.openURL(url); #end @@ -848,6 +848,104 @@ class CoolUtil var file = new haxe.io.Path(file); return file.file; } + + /** + * Converts a string of "1..3,5,7..9,8..5" into an array of numbers like [1,2,3,5,7,8,9,8,7,6,5] + * @param input String to parse + * @return Array of numbers + */ + public static function parseNumberRange(input:String):Array { + var result:Array = []; + var parts:Array = input.split(","); + + for (part in parts) { + part = part.trim(); + var idx = part.indexOf(".."); + if (idx != -1) { + var start = Std.parseInt(part.substring(0, idx).trim()); + var end = Std.parseInt(part.substring(idx + 2).trim()); + + if(start == null || end == null) { + continue; + } + + if (start < end) { + for (j in start...end + 1) { + result.push(j); + } + } else { + for (j in end...start + 1) { + result.push(start + end - j); + } + } + } else { + var num = Std.parseInt(part); + if (num != null) { + result.push(num); + } + } + } + return result; + } + + /** + * Converts an array of numbers into a string of ranges. + * Example: [1,2,3,5,7,8,9,8,7,6,5] -> "1..3,5,7..9,8..5" + * @param numbers Array of numbers + * @return String representing the ranges + */ + public static function formatNumberRange(numbers:Array, seperator:String = ","):String { + if (numbers.length == 0) return ""; + + var result:Array = []; + var i = 0; + + while (i < numbers.length) { + var start = numbers[i]; + var end = start; + var direction = 0; // 0: no sequence, 1: increasing, -1: decreasing + + if (i + 1 < numbers.length) { // detect direction of sequence + if (numbers[i + 1] == end + 1) { + direction = 1; + } else if (numbers[i + 1] == end - 1) { + direction = -1; + } + } + + if(direction != 0) { + while (i + 1 < numbers.length && (numbers[i + 1] == end + direction)) { + end = numbers[i + 1]; + i++; + } + } + + if (start == end) { // no direction + result.push('${start}'); + } else if (start + direction == end) { // 1 step increment + result.push('${start},${end}'); + } else { // store as range + result.push('${start}..${end}'); + } + + i++; + } + + return result.join(seperator); + } + + public static function deepFlatten(arr:Array, ?result:Array):Array { + if(arr == null) return []; + if(result == null) result = []; + for (e in arr) { + if (Std.isOfType(e, Array)) { + deepFlatten(e, result); + } else { + result.push(e); + } + } + return result; + } } /** @@ -860,4 +958,4 @@ enum abstract CoolSfx(Int) from Int { var CHECKED = 3; var UNCHECKED = 4; var WARNING = 5; -} \ No newline at end of file +} diff --git a/source/funkin/backend/utils/FunkinParentDisabler.hx b/source/funkin/backend/utils/FunkinParentDisabler.hx index 07e71a2a..8e9554cc 100644 --- a/source/funkin/backend/utils/FunkinParentDisabler.hx +++ b/source/funkin/backend/utils/FunkinParentDisabler.hx @@ -32,8 +32,8 @@ class FunkinParentDisabler extends FlxBasic { FlxTimer.globalManager._timers = []; // cameras - __cameras = [for(c in FlxG.cameras.list) if (c.active) c]; - for(c in __cameras) c.active = false; + __cameras = [for(c in FlxG.cameras.list) if (!c.paused) c]; + for(c in __cameras) c.paused = true; // sounds __sounds = [for(s in FlxG.sound.list) if (s.playing) s]; @@ -41,9 +41,6 @@ class FunkinParentDisabler extends FlxBasic { } } - public override function update(elapsed:Float) { - @:privateAccess for(c in __cameras) c.updateFlashSpritePosition(); - } public override function draw() {} public function reset() { @@ -63,7 +60,7 @@ class FunkinParentDisabler extends FlxBasic { for(t in __tweens) FlxTween.globalManager._tweens.push(t); for(t in __timers) FlxTimer.globalManager._timers.push(t); } - for(c in __cameras) c.active = true; + for(c in __cameras) c.paused = false; for(s in __sounds) s.play(); } } diff --git a/source/funkin/backend/utils/IniUtil.hx b/source/funkin/backend/utils/IniUtil.hx index 1777ffb8..5f13640b 100644 --- a/source/funkin/backend/utils/IniUtil.hx +++ b/source/funkin/backend/utils/IniUtil.hx @@ -29,7 +29,7 @@ class IniUtil { var name = line.substr(0, index).trim(); var value = line.substr(index+1).trim(); - if (value.startsWith("\"") && value.endsWith("\"")) + if (value.length > 1 && value.charCodeAt(0) == '"'.code && value.charCodeAt(value.length-1) == '"'.code) value = value.substr(1, value.length - 2); if (value.length == 0 || name.length == 0) diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx index cc8fb9d2..6c27629e 100644 --- a/source/funkin/backend/utils/MemoryUtil.hx +++ b/source/funkin/backend/utils/MemoryUtil.hx @@ -117,7 +117,17 @@ class MemoryUtil { 22 => "DDR2 FB-DIMM", 24 => "DDR3", 25 => "FBD2", - 26 => "DDR4" + 26 => "DDR4", + 27 => "LPDDR", + 28 => "LPDDR2", + 29 => "LPDDR3", + 30 => "LPDDR4", + 31 => "Logical Non-volatile device", + 32 => "HBM", + 33 => "HBM2", + 34 => "DDR5", + 35 => "LPDDR5", + 36 => "HBM3", ]; var memoryOutput:Int = -1; diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index feb7c780..0bdd3622 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -4,6 +4,7 @@ import flixel.util.FlxColor; import flixel.util.typeLimit.OneOfTwo; import funkin.backend.FunkinSprite.XMLAnimType; import funkin.backend.FunkinSprite; +import funkin.game.Character; import funkin.backend.system.ErrorCode; import funkin.backend.system.interfaces.IOffsetCompatible; import haxe.xml.Access; @@ -170,8 +171,7 @@ class XMLUtil { animType: spr.spriteAnimType, x: 0, y: 0, - indices: [for(i in 0...spr.frames.frames.length) i], - forced: (node.has.forced && node.att.forced == "true") || (!node.has.forced && spr.spriteAnimType == BEAT) + indices: [for(i in 0...spr.frames.frames.length) i] }); } } @@ -197,8 +197,7 @@ class XMLUtil { animType: animType, x: 0, y: 0, - indices: [], - forced: false, + indices: [] }; if (anim.has.name) animData.name = anim.att.name; @@ -209,14 +208,7 @@ class XMLUtil { if (anim.has.y) animData.y = Std.parseFloat(anim.att.y).getDefault(animData.y); if (anim.has.loop) animData.loop = anim.att.loop == "true"; if (anim.has.forced) animData.forced = anim.att.forced == "true"; - if (anim.has.indices) { - var indicesSplit = anim.att.indices.split(","); - for(indice in indicesSplit) { - var i = Std.parseInt(indice.trim()); - if (i != null) - animData.indices.push(i); - } - } + if (anim.has.indices) animData.indices = CoolUtil.parseNumberRange(anim.att.indices); return animData; } @@ -261,24 +253,28 @@ class XMLUtil { if (sprite is FunkinSprite) { var xmlSpr = cast(sprite, FunkinSprite); + var name = animData.name; switch(animData.animType) { case BEAT: xmlSpr.beatAnims.push({ - name: animData.name, - forced: animData.forced.getDefault(false) + name: name, + forced: animData.forced.getDefault(defaultForcedCheck(name, xmlSpr)) }); case LOOP: - xmlSpr.playAnim(animData.name, animData.forced.getDefault(false)); + xmlSpr.playAnim(name, animData.forced.getDefault(defaultForcedCheck(name, xmlSpr))); default: // nothing } - xmlSpr.animDatas.set(animData.name, animData); + xmlSpr.animDatas.set(name, animData); } return OK; } return MISSING_PROPERTY; } + public static inline function defaultForcedCheck(animName:String, sprite:FunkinSprite):Bool + return sprite is Character && (animName.startsWith("idle") || animName.startsWith("danceLeft") || animName.startsWith("danceRight")) ? false : sprite.spriteAnimType == BEAT; + public static inline function fixXMLText(text:String) { var v:String; return [for(l in text.split("\n")) if ((v = l.trim()) != "") v].join("\n"); diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index 1d9768e3..45505fea 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -33,6 +33,14 @@ class EditorPicker extends MusicBeatSubstate { state: UIDebugState }, #end + { + name: "Wiki", + iconID: 5, + state: null, + onClick: function() { + CoolUtil.openURL("https://fnf-cne-devs.github.io/"); + } + }, { name: "Debug Options", id: "debugOptions", @@ -102,7 +110,9 @@ class EditorPicker extends MusicBeatSubstate { } if (controls.ACCEPT || FlxG.mouse.justReleased) { - if (options[curSelected].state != null) { + if(options[curSelected].onClick != null) + options[curSelected].onClick(); + else if (options[curSelected].state != null) { selected = true; CoolUtil.playMenuSFX(CONFIRM); @@ -154,6 +164,7 @@ typedef Editor = { var id:String; var iconID:Int; var state:Class; + var ?onClick:Void->Void; } class EditorPickerOption extends FlxTypedSpriteGroup { @@ -170,7 +181,6 @@ class EditorPickerOption extends FlxTypedSpriteGroup { public function new(name:String, iconID:Int, height:Float) { super(); - FlxG.mouse.visible = true; iconSpr = new FlxSprite(); iconSpr.loadGraphic(Paths.image('editors/icons'), true, 128, 128); diff --git a/source/funkin/editors/character/CharacterAnimScreen.hx b/source/funkin/editors/character/CharacterAnimScreen.hx index 9968a11e..0d9f30da 100644 --- a/source/funkin/editors/character/CharacterAnimScreen.hx +++ b/source/funkin/editors/character/CharacterAnimScreen.hx @@ -72,7 +72,7 @@ class CharacterAnimScreen extends UISubstateWindow { addLabelOn(loopedCheckbox, translate("looping")); loopedCheckbox.x += 8; loopedCheckbox.y += 6; - indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y + 32 + 40, animData.indices.join(", "), 270); + indicesTextBox = new UITextBox(nameTextBox.x, nameTextBox.y + 32 + 40, CoolUtil.formatNumberRange(animData.indices, ", "), 270); add(indicesTextBox); addLabelOn(indicesTextBox, translate("indices")); @@ -112,14 +112,8 @@ class CharacterAnimScreen extends UISubstateWindow { animType: NONE, x: offsetXStepper.value, y: offsetYStepper.value, - indices: [] + indices: CoolUtil.parseNumberRange(indicesTextBox.label.text) }; - var indicesSplit = indicesTextBox.label.text.split(","); - for(indice in indicesSplit) { - var i = Std.parseInt(indice.trim()); - if (i != null) - animData.indices.push(i); - } if (onSave != null) onSave(animData); } diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index dfc3f655..1997a5cd 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -195,8 +195,10 @@ class CharacterInfoScreen extends UISubstateWindow { var offset:FlxPoint = character.getAnimOffset(anim.name); animXml.set("x", Std.string(offset.x)); animXml.set("y", Std.string(offset.y)); + offset.put(); + if (anim.indices.length > 0) - animXml.set("indices", anim.indices.join(",")); + animXml.set("indices", CoolUtil.formatNumberRange(anim.indices)); xml.addChild(animXml); } diff --git a/source/funkin/editors/charter/SongCreationScreen.hx b/source/funkin/editors/charter/SongCreationScreen.hx index b1d7e5a1..0f2b78ab 100644 --- a/source/funkin/editors/charter/SongCreationScreen.hx +++ b/source/funkin/editors/charter/SongCreationScreen.hx @@ -86,8 +86,6 @@ class SongCreationScreen extends UISubstateWindow { stepsPerBeatStepper = new UINumericStepper(beatsPerMeasureStepper.x + 30 + 24, beatsPerMeasureStepper.y, 4, 1, 0, 1, null, 54); songDataGroup.add(stepsPerBeatStepper); - var voicesUIText:UIText = null; - instExplorer = new UIFileExplorer(songNameTextBox.x, songNameTextBox.y + 32 + 36, null, null, Paths.SOUND_EXT, function (res) { var audioPlayer:UIAudioPlayer = new UIAudioPlayer(instExplorer.x + 8, instExplorer.y + 8, res); instExplorer.members.push(audioPlayer); @@ -105,10 +103,13 @@ class SongCreationScreen extends UISubstateWindow { }); songDataGroup.add(voicesExplorer); - voicesUIText = addLabelOn(voicesExplorer, ""); + /*voicesUIText = addLabelOn(voicesExplorer, ""); voicesUIText.applyMarkup( translate("voicesAudio"), - [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]); + [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFAD1212), "$")]);*/ + + addLabelOn(voicesExplorer, "Vocal Audio File"); + var menuTitle:UIText; menuDataGroup.add(menuTitle = new UIText(windowSpr.x + 20, windowSpr.y + 30 + 16, 0, translateMeta("menusData"), 28)); diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 50d82f88..d043c040 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -218,7 +218,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset /** * Like `playSingAnimUnsafe` but checks if the character has the animation with the suffix part, otherwise it plays the animation without the suffix part. */ - public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, Force:Bool = true, Reversed:Bool = false, Frame:Int = 0) + public function playSingAnim(direction:Int, suffix:String = "", Context:PlayAnimContext = SING, ?Force:Null = null, Reversed:Bool = false, Frame:Int = 0) { var event = EventManager.get(DirectionAnimEvent).recycle(getSingAnim(direction, suffix), direction, suffix, Context, Reversed, Frame, Force); script.call("onPlaySingAnim", [event]); @@ -235,7 +235,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset playAnim(event.animName, event.force, event.context, event.reversed, event.frame); } - public override function playAnim(AnimName:String, Force:Bool = false, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { + public override function playAnim(AnimName:String, ?Force:Bool, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context); script.call("onPlayAnim", [event]); if (event.cancelled) return; @@ -306,6 +306,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public inline function buildCharacter(xml:Access) { this.xml = xml; // Modders wassup :D sprite = curCharacter; + spriteAnimType = BEAT; if (xml.x.exists("isPlayer")) playerOffsets = (xml.x.get("isPlayer") == "true"); if (xml.x.exists("x")) globalOffset.x = Std.parseFloat(xml.x.get("x")); @@ -396,7 +397,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset offset.putWeak(); if (anim.indices.length > 0) - animXml.set("indices", anim.indices.join(",")); + animXml.set("indices", CoolUtil.formatNumberRange(anim.indices)); xml.addChild(animXml); } diff --git a/source/funkin/game/NoteGroup.hx b/source/funkin/game/NoteGroup.hx index 98b6477e..9c974a13 100644 --- a/source/funkin/game/NoteGroup.hx +++ b/source/funkin/game/NoteGroup.hx @@ -34,10 +34,16 @@ class NoteGroup extends FlxTypedGroup { return FlxSort.byValues(FlxSort.DESCENDING, n1.strumTime, n2.strumTime); }); } + + @:dox(hide) public var __forcedSongPos:Null = null; + + @:dox(hide) private inline function __getSongPos() + return __forcedSongPos == null ? Conductor.songPosition : __forcedSongPos; + public override function update(elapsed:Float) { i = length-1; __loopSprite = null; - __time = Conductor.songPosition; + __time = __getSongPos(); while(i >= 0) { __loopSprite = members[i--]; if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.active) { @@ -58,7 +64,7 @@ class NoteGroup extends FlxTypedGroup { i = length-1; __loopSprite = null; - __time = Conductor.songPosition; + __time = __getSongPos(); while(i >= 0) { __loopSprite = members[i--]; if (__loopSprite == null || !__loopSprite.exists || !__loopSprite.visible) @@ -81,7 +87,7 @@ class NoteGroup extends FlxTypedGroup { public override function forEach(noteFunc:Note->Void, recursive:Bool = false) { i = length-1; __loopSprite = null; - __time = Conductor.songPosition; + __time = __getSongPos(); var oldCur = __currentlyLooping; __currentlyLooping = true; diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index bac69c8b..346b701e 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -242,7 +242,7 @@ class PlayState extends MusicBeatState /** * Current stage name */ - public var curStage:String = ""; + public var curStage(get, set):String; /** * Interval at which Girlfriend dances. @@ -362,7 +362,7 @@ class PlayState extends MusicBeatState * Speed at which the game camera zoom lerps to. */ public var camGameZoomLerp:Float = 0.05; - + /** * Camera zoom at which the hud lerps to. */ @@ -525,6 +525,14 @@ class PlayState extends MusicBeatState return this.maxHealth = v; } + private inline function get_curStage() + return stage == null ? "" : stage.stageName; + + private inline function set_curStage(name:String) { + if (stage != null) stage.stageName = name; + return name; + } + @:dox(hide) override public function create() { Note.__customNoteTypeExists = []; @@ -653,10 +661,12 @@ class PlayState extends MusicBeatState chars.push(char); } - var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos; - var startingPos:FlxPoint = strumLine.strumPos == null ? - FlxPoint.get((FlxG.width * strOffset) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2), this.strumLine.y) : - FlxPoint.get(strumLine.strumPos[0] == 0 ? ((FlxG.width * strOffset) - ((Note.swagWidth * (strumLine.strumScale == null ? 1 : strumLine.strumScale)) * 2)) : strumLine.strumPos[0], strumLine.strumPos[1]); + var strOffset:Float = strumLine.strumLinePos != null ? strumLine.strumLinePos : (strumLine.type == 1 ? 0.75 : 0.25); + var strScale:Float = strumLine.strumScale != null ? strumLine.strumScale : 1; + var strXPos:Float = (FlxG.width * strOffset) - (Note.swagWidth * strScale * 2); + var startingPos:FlxPoint = strumLine.strumPos != null ? + FlxPoint.get(strumLine.strumPos[0] == 0 ? strXPos : strumLine.strumPos[0], strumLine.strumPos[1]) : + FlxPoint.get(strXPos, this.strumLine.y); var strLine = new StrumLine(chars, startingPos, strumLine.strumScale == null ? 1 : strumLine.strumScale, @@ -1261,7 +1271,7 @@ class PlayState extends MusicBeatState if (Conductor.songPosition >= 0) startSong(); } - } else { + } else if (FlxG.sound.music != null) { var instTime = FlxG.sound.music.time; var isOffsync = vocals.time != instTime || [for(strumLine in strumLines.members) strumLine.vocals.time != instTime].contains(true); __vocalOffsetViolation = Math.max(0, __vocalOffsetViolation + (isOffsync ? elapsed : -elapsed / 2)); @@ -1924,7 +1934,7 @@ class PlayState extends MusicBeatState } } -class ComboRating { +final class ComboRating { public var percent:Float; public var rating:String; public var color:FlxColor; diff --git a/source/funkin/game/SplashHandler.hx b/source/funkin/game/SplashHandler.hx index 1776bdb8..31a06234 100644 --- a/source/funkin/game/SplashHandler.hx +++ b/source/funkin/game/SplashHandler.hx @@ -1,5 +1,7 @@ package funkin.game; +import funkin.backend.scripting.events.SplashShowEvent; + class SplashHandler extends FlxTypedGroup { /** * Map containing all of the splashes group. @@ -40,10 +42,10 @@ class SplashHandler extends FlxTypedGroup { var __grp:SplashGroup; public function showSplash(name:String, strum:Strum) { __grp = getSplashGroup(name); - var event = funkin.backend.scripting.EventManager.get(funkin.backend.scripting.events.DynamicEvent).recycle(__grp.showOnStrum(strum), strum); + var event = EventManager.get(SplashShowEvent).recycle(name, __grp.showOnStrum(strum), strum, __grp); event = PlayState.instance.scripts.event("onSplashShown", event); if (!event.cancelled) - add(event.value1); + add(event.splash); // max 8 rendered splashes while(members.length > 8) diff --git a/source/funkin/game/Stage.hx b/source/funkin/game/Stage.hx index 2e3dd050..90f3c748 100644 --- a/source/funkin/game/Stage.hx +++ b/source/funkin/game/Stage.hx @@ -2,6 +2,7 @@ package funkin.game; import flixel.FlxState; import flixel.math.FlxPoint; +import funkin.backend.scripting.DummyScript; import funkin.backend.scripting.Script; import funkin.backend.scripting.events.StageNodeEvent; import funkin.backend.scripting.events.StageXMLEvent; @@ -12,18 +13,22 @@ import haxe.xml.Access; using StringTools; class Stage extends FlxBasic implements IBeatReceiver { + public var stageName:String = ""; public var stageXML:Access; public var stagePath:String; public var stageSprites:Map = []; public var stageScript:Script; public var state:FlxState; public var characterPoses:Map = []; + public var xmlImportedScripts:Array = []; private var spritesParentFolder = ""; - public function getSprite(name:String) { + public function getSprite(name:String) return stageSprites[name]; - } + + public function setStagesSprites(script:Script) + for (k=>e in stageSprites) script.set(k, e); public function new(stage:String, ?state:FlxState) { super(); @@ -33,11 +38,8 @@ class Stage extends FlxBasic implements IBeatReceiver { this.state = state; stagePath = Paths.xml('stages/$stage'); - try { - if (Assets.exists(stagePath)) stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement()); - } catch(e) { - Logs.error('Couldn\'t load stage "$stage": ${e.message}'); - } + try if (Assets.exists(stagePath)) stageXML = new Access(Xml.parse(Assets.getText(stagePath)).firstElement()) + catch(e) Logs.error('Couldn\'t load stage "$stage": ${e.message}'); if (PlayState.instance != null) { stageScript = Script.create(Paths.script('data/stages/$stage')); @@ -45,13 +47,15 @@ class Stage extends FlxBasic implements IBeatReceiver { stageScript.load(); } + var event = null; if (stageXML != null) { + stageName = stageXML.getAtt("name").getDefault(stage); + if (PlayState.instance != null) { var parsed:Null; if(stageXML.has.startCamPosX && (parsed = Std.parseFloat(stageXML.att.startCamPosX)) != null) PlayState.instance.camFollow.x = parsed; if(stageXML.has.startCamPosY && (parsed = Std.parseFloat(stageXML.att.startCamPosY)) != null) PlayState.instance.camFollow.y = parsed; if(stageXML.has.zoom && (parsed = Std.parseFloat(stageXML.att.zoom)) != null) PlayState.instance.defaultCamZoom = parsed; - PlayState.instance.curStage = stageXML.has.name ? stageXML.att.name : stage; } if (stageXML.has.folder) { spritesParentFolder = stageXML.att.folder; @@ -60,16 +64,13 @@ class Stage extends FlxBasic implements IBeatReceiver { var elems = []; for(node in stageXML.elements) { - if (node.name == "high-memory" && !Options.lowMemoryMode) - for(e in node.elements) - elems.push(e); - else - elems.push(node); + if (node.name == "high-memory" && !Options.lowMemoryMode) for(e in node.elements) __pushNcheckNode(elems, e); + else __pushNcheckNode(elems, node); } if (PlayState.instance != null) { - var event = PlayState.instance.scripts.event("onStageXMLParsed", EventManager.get(StageXMLEvent).recycle(this, stageXML, elems)); - elems = event.elems; + event = EventManager.get(StageXMLEvent).recycle(this, stageXML, elems); + elems = PlayState.instance.scripts.event("onStageXMLParsed", event).elems; } for(node in elems) { @@ -86,7 +87,7 @@ class Stage extends FlxBasic implements IBeatReceiver { state.add(spr); spr; case "box" | "solid": - if ( !node.has.name || !node.has.width || !node.has.height) continue; + if (!node.has.name || !node.has.width || !node.has.height) continue; var spr = new FlxSprite( (node.has.x) ? Std.parseFloat(node.att.x).getDefault(0) : 0, @@ -134,6 +135,9 @@ class Stage extends FlxBasic implements IBeatReceiver { ); PlayState.instance.add(PlayState.instance.comboGroup); PlayState.instance.comboGroup; + case "use-extension" | "extension" | "ext": + if (__shouldLoadBefore(node) || XMLImportedScriptInfo.prepareInfos(node, this) == null) continue; + null; default: null; } @@ -173,11 +177,37 @@ class Stage extends FlxBasic implements IBeatReceiver { }); if (PlayState.instance == null) return; - for(k=>e in stageSprites) { - stageScript.set(k, e); + + setStagesSprites(stageScript); + + // i know this for gets run twice under, but its better like this in case a script modifies the short lived ones, i dont wanna save them in an array; more dynamic like this - Nex + for (info in xmlImportedScripts) if (info.importStageSprites) { + var script = info.getScript(); + if (script != null) setStagesSprites(script); + } + + // idk lemme check anyways just in case scripts did smth - Nex + if (event != null) PlayState.instance.scripts.event("onPostStageCreation", event); + + // shortlived scripts destroy when the stage finishes setting up - Nex + for (info in xmlImportedScripts) if (info.shortLived) { + var script = info.getScript(); + if (script == null) continue; + + PlayState.instance.scripts.remove(script); + script.destroy(); } } + @:dox(hide) private function __pushNcheckNode(array:Array, node:Access) { + array.push(node); + if ((node.name == "use-extension" || node.name == "extension" || node.name == "ext") && __shouldLoadBefore(node)) + XMLImportedScriptInfo.prepareInfos(node, this); + } + + @:dox(hide) private static inline function __shouldLoadBefore(node:Access):Bool + return node.getAtt("loadBefore") != "false"; + public function addCharPos(name:String, node:Access, ?nonXMLInfo:StageCharPosInfo):StageCharPos { var charPos = new StageCharPos(); charPos.visible = charPos.active = false; @@ -287,4 +317,45 @@ typedef StageCharPosInfo = { var y:Float; var flip:Bool; var scroll:Float; +} + +class XMLImportedScriptInfo { + public var path:String; + public var shortLived:Bool = false; + public var loadBefore:Bool = true; + public var importStageSprites:Bool = false; + + public function new(path:String) + this.path = path; + + public function getScript():Script + return PlayState.instance == null ? null : PlayState.instance.scripts.getByPath(path); + + public static function prepareInfos(node:Access, ?stage:Stage):XMLImportedScriptInfo { + if (!node.has.script || PlayState.instance == null) return null; + + var folder = node.getAtt("folder").getDefault("data/scripts/"); + if (!folder.endsWith("/")) folder += "/"; + + var path = Paths.script(folder + node.getAtt("script")); + var daScript = Script.create(path); + if (daScript is DummyScript) { + var msg = 'Script Extension at ${path} does not exist'; + if (stage != null) msg += ' through stage "${stage.stageName}"'; + Logs.trace(msg + ".", ERROR); + return null; + } + + var infos = new XMLImportedScriptInfo(daScript.path); + infos.shortLived = node.getAtt("isShortLived") == "true"; + infos.importStageSprites = node.getAtt("importStageSprites") == "true"; + @:privateAccess infos.loadBefore = Stage.__shouldLoadBefore(node); + + if (stage != null) stage.xmlImportedScripts.push(infos); + PlayState.instance.scripts.add(daScript); + daScript.set("scriptInfo", infos); + daScript.load(); + + return infos; + } } \ No newline at end of file diff --git a/source/funkin/game/StrumLine.hx b/source/funkin/game/StrumLine.hx index dd952d19..cca45b51 100644 --- a/source/funkin/game/StrumLine.hx +++ b/source/funkin/game/StrumLine.hx @@ -152,7 +152,10 @@ class StrumLine extends FlxTypedGroup { var scrollSpeed = strumLine.scrollSpeed; if(scrollSpeed == null) if (PlayState.instance != null) scrollSpeed = PlayState.instance.scrollSpeed; if(scrollSpeed == null) scrollSpeed = 1; - notes.limit = 1500 / scrollSpeed; + + // TODOD: Make this work by accounting zoom and scrollspeed changes - Nex + /*var limit = FlxG.height / 0.45; + notes.limit = limit / scrollSpeed;*/ } public override function update(elapsed:Float) { diff --git a/source/funkin/game/cutscenes/dialogue/DialogueBox.hx b/source/funkin/game/cutscenes/dialogue/DialogueBox.hx index be917a80..9484e9fc 100644 --- a/source/funkin/game/cutscenes/dialogue/DialogueBox.hx +++ b/source/funkin/game/cutscenes/dialogue/DialogueBox.hx @@ -94,7 +94,7 @@ class DialogueBox extends FunkinSprite { dialogueBoxScript.call("postCreate"); } - public override function playAnim(AnimName:String, Force:Bool = false, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { + public override function playAnim(AnimName:String, ?Force:Bool, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context); dialogueBoxScript.call("onPlayAnim", [event]); if(event.cancelled) return; diff --git a/source/funkin/game/cutscenes/dialogue/DialogueCharacter.hx b/source/funkin/game/cutscenes/dialogue/DialogueCharacter.hx index 8263272f..e075bef9 100644 --- a/source/funkin/game/cutscenes/dialogue/DialogueCharacter.hx +++ b/source/funkin/game/cutscenes/dialogue/DialogueCharacter.hx @@ -61,7 +61,7 @@ class DialogueCharacter extends FunkinSprite { dialogueCharScript.call("postCreate"); } - public override function playAnim(AnimName:String, Force:Bool = false, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { + public override function playAnim(AnimName:String, ?Force:Bool, Context:PlayAnimContext = NONE, Reversed:Bool = false, Frame:Int = 0) { var event = EventManager.get(PlayAnimEvent).recycle(AnimName, Force, Reversed, Frame, Context); dialogueCharScript.call("playAnim", [event]); if(event.cancelled) return; diff --git a/source/funkin/import.hx b/source/funkin/import.hx index a83d7c8a..20059ad2 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -23,6 +23,8 @@ import flixel.math.FlxMath; import flixel.tweens.FlxEase; import flixel.util.FlxDestroyUtil; +import funkin.backend.system.Flags; + import funkin.menus.ui.Alphabet; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index fb85e1fb..dfa2a32f 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -465,8 +465,17 @@ class FreeplaySonglist { public static function get(useTxt:Bool = true) { var songList = new FreeplaySonglist(); - if (songList.getSongsFromSource(MODS, useTxt)) - songList.getSongsFromSource(SOURCE, useTxt); + switch(Flags.SONGS_LIST_MOD_MODE) { + case 'prepend': + songList.getSongsFromSource(MODS, useTxt); + songList.getSongsFromSource(SOURCE, useTxt); + case 'append': + songList.getSongsFromSource(SOURCE, useTxt); + songList.getSongsFromSource(MODS, useTxt); + default /*case 'override'*/: + if (songList.getSongsFromSource(MODS, useTxt)) + songList.getSongsFromSource(SOURCE, useTxt); + } return songList; } diff --git a/source/funkin/menus/MainMenuState.hx b/source/funkin/menus/MainMenuState.hx index ab643c99..509a9d82 100644 --- a/source/funkin/menus/MainMenuState.hx +++ b/source/funkin/menus/MainMenuState.hx @@ -167,7 +167,7 @@ class MainMenuState extends MusicBeatState { case 'story mode': FlxG.switchState(new StoryMenuState()); case 'freeplay': FlxG.switchState(new FreeplayState()); - case 'donate': FlxG.switchState(new CreditsMain()); + case 'donate', 'credits': FlxG.switchState(new CreditsMain()); // kept donate for not breaking scripts, if you dont want donate to bring you to the credits menu, thats easy softcodable - Nex case 'options': FlxG.switchState(new OptionsMenu()); } }); diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index 094eb07b..1cd7b809 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -8,6 +8,7 @@ import funkin.backend.scripting.Script; import funkin.backend.scripting.events.MenuChangeEvent; import funkin.backend.scripting.events.NameEvent; import funkin.backend.scripting.events.PauseCreationEvent; +import funkin.backend.system.Conductor; import funkin.backend.utils.FunkinParentDisabler; import funkin.editors.charter.Charter; import funkin.menus.StoryMenuState; @@ -169,6 +170,9 @@ class PauseSubState extends MusicBeatSubstate PlayState.resetSongInfos(); if (Charter.instance != null) Charter.instance.__clearStatics(); + // prevents certain notes to disappear early when exiting - Nex + game.strumLines.forEachAlive(function(grp) grp.notes.__forcedSongPos = Conductor.songPosition); + CoolUtil.playMenuSong(); FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState()); } diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx index 7e5cc9a8..36baa263 100644 --- a/source/funkin/menus/StoryMenuState.hx +++ b/source/funkin/menus/StoryMenuState.hx @@ -210,14 +210,24 @@ class StoryMenuState extends MusicBeatState { } } - intendedScore = FunkinSave.getWeekHighscore(weeks[curWeek].name, weeks[curWeek].difficulties[curDifficulty]).score; + intendedScore = FunkinSave.getWeekHighscore(weeks[curWeek].id, weeks[curWeek].difficulties[curDifficulty]).score; } public function loadXMLs() { // CoolUtil.coolTextFile(Paths.txt('freeplaySonglist')); var weeks:Array = []; - if (getWeeksFromSource(weeks, MODS)) - getWeeksFromSource(weeks, SOURCE); + + switch(Flags.WEEKS_LIST_MOD_MODE) { + case 'prepend': + getWeeksFromSource(weeks, MODS); + getWeeksFromSource(weeks, SOURCE); + case 'append': + getWeeksFromSource(weeks, SOURCE); + getWeeksFromSource(weeks, MODS); + default /*case 'override'*/: + if (getWeeksFromSource(weeks, MODS)) + getWeeksFromSource(weeks, SOURCE); + } for(k=>weekName in weeks) { var week:Access = null; @@ -355,8 +365,8 @@ class StoryMenuState extends MusicBeatState { } typedef WeekData = { - var name:String; - var id:String; + var name:String; // name SHOULD NOT be used for loading week highscores, its just the name on the right side of the week, remember that next time!! - Nex + var id:String; // id IS instead for saving and loading!! - Nex var sprite:String; var chars:Array; var songs:Array; diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx index 841af11e..813e6296 100644 --- a/source/funkin/menus/credits/CreditsMain.hx +++ b/source/funkin/menus/credits/CreditsMain.hx @@ -5,9 +5,11 @@ import funkin.options.OptionsScreen; import funkin.options.TreeMenu; import funkin.options.type.*; import haxe.xml.Access; +import flixel.util.FlxColor; class CreditsMain extends TreeMenu { var bg:FlxSprite; + var items:Array = []; public override function create() { bg = new FlxSprite(-80).loadGraphic(Paths.image('menus/menuBGBlue')); @@ -19,7 +21,6 @@ class CreditsMain extends TreeMenu { bg.antialiasing = true; add(bg); - var selectables:Array = []; var xmlPath = Paths.xml('config/credits'); for(source in [funkin.backend.assets.AssetSource.SOURCE, funkin.backend.assets.AssetSource.MODS]) { if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { @@ -32,17 +33,18 @@ class CreditsMain extends TreeMenu { if (access != null) for(c in parseCreditsFromXML(access, source)) - selectables.push(c); + items.push(c); } } - selectables.push(new TextOption("Codename Engine >", TU.translate("credits.selectCodename"), function() { + + items.push(new TextOption("Codename Engine >", TU.translate("credits.selectCodename"), function() { optionsTree.add(Type.createInstance(CreditsCodename, [])); })); - selectables.push(new TextOption("Friday Night Funkin'", TU.translate("credits.selectBase"), function() { + items.push(new TextOption("Friday Night Funkin'", TU.translate("credits.selectBase"), function() { CoolUtil.openURL("https://ninja-muffin24.itch.io/funkin"); })); - main = new OptionsScreen(TU.translate("credits.name"), TU.translate("credits.madePossible"), selectables); + main = new OptionsScreen(TU.translate("credits.name"), TU.translate("credits.madePossible"), items); super.create(); DiscordUtil.call("onMenuLoaded", ["Credits Menu"]); @@ -69,11 +71,13 @@ class CreditsMain extends TreeMenu { html_url: 'https://github.com/$username', avatar_url: 'https://github.com/$username.png' }; - - credsMenus.push(new GithubIconOption(user, desc, null, + var opt:GithubIconOption = new GithubIconOption(user, desc, null, node.has.customName ? node.att.customName : null, node.has.size ? Std.parseInt(node.att.size) : 96, node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true - )); + ); + if (node.has.color) + @:privateAccess opt.__text.color = FlxColor.fromString(node.att.color); + credsMenus.push(opt); } else { if (!node.has.name) { Logs.warn("A credit node requires a name attribute.", "CreditsMain"); @@ -83,11 +87,14 @@ class CreditsMain extends TreeMenu { switch(node.name) { case "credit": - credsMenus.push(new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url), + var opt:PortraitOption = new PortraitOption(name, desc, function() if(node.has.url) CoolUtil.openURL(node.att.url), node.has.icon && Paths.assetsTree.existsSpecific(Paths.image('credits/${node.att.icon}'), "IMAGE", source) ? FlxG.bitmap.add(Paths.image('credits/${node.att.icon}')) : null, node.has.size ? Std.parseInt(node.att.size) : 96, node.has.portrait ? node.att.portrait.toLowerCase() == "false" ? false : true : true - )); + ); + if (node.has.color) + @:privateAccess opt.__text.color = FlxColor.fromString(node.att.color); + credsMenus.push(opt); case "menu": credsMenus.push(new TextOption(name + " >", desc, function() { @@ -99,4 +106,4 @@ class CreditsMain extends TreeMenu { return credsMenus; } -} \ No newline at end of file +} diff --git a/source/hscript/Config.hx b/source/hscript/Config.hx index dcfcc10e..98fc7f7a 100644 --- a/source/hscript/Config.hx +++ b/source/hscript/Config.hx @@ -3,19 +3,23 @@ package hscript; class Config { // Runs support for custom classes in these public static final ALLOWED_CUSTOM_CLASSES = [ + #if !DOCUMENTATION "flixel", "funkin", + #end ]; // Runs support for abstract support in these public static final ALLOWED_ABSTRACT_AND_ENUM = [ + #if !DOCUMENTATION "flixel", "openfl", "haxe.xml", "haxe.CallStack", "funkin", + #end ]; // Incase any of your files fail