diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a8452f77..76adeded 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -2,6 +2,7 @@ name: Linux Build on: push: + workflow_dispatch: jobs: build: name: Linux Build @@ -34,16 +35,18 @@ jobs: - name: Building the game run: | haxelib run lime build linux + - name: Tar files + run: tar -zcvf CodenameEngine.tar.gz export/release/linux/bin - name: Uploading artifact (entire build) uses: actions/upload-artifact@v2 with: name: Codename Engine - path: export/release/linux/bin - - name: Uploading artifact (executable) - uses: actions/upload-artifact@v2 - with: - name: Update-Linux - path: export/release/linux/bin/CodenameEngine + path: CodenameEngine.tar.gz + # - name: Uploading artifact (executable) + # uses: actions/upload-artifact@v2 + # with: + # name: Update-Linux + # path: export/release/linux/bin/CodenameEngine - name: Clearing already existing cache uses: actions/github-script@v6 with: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 65a3fa2e..e6646418 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,6 +2,7 @@ name: Mac OS Build on: push: + workflow_dispatch: jobs: build: name: Mac OS Build @@ -31,16 +32,18 @@ jobs: - name: Building the game run: | haxelib run lime build mac + - name: Tar files + run: tar -zcvf CodenameEngine.tar.gz export/release/macos/bin - name: Uploading artifact (entire build) uses: actions/upload-artifact@v2 with: name: Codename Engine - path: export/release/macos/bin - - name: Uploading artifact (executable) - uses: actions/upload-artifact@v2 - with: - name: Update-Mac - path: export/release/macos/bin/CodenameEngine + path: CodenameEngine.tar.gz + # - name: Uploading artifact (executable) + # uses: actions/upload-artifact@v2 + # with: + # name: Update-Mac + # path: export/release/macos/bin/CodenameEngine - name: Clearing already existing cache uses: actions/github-script@v6 with: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 6925d8ef..d89c1687 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -37,11 +37,11 @@ jobs: with: name: Codename Engine path: export/release/windows/bin - - name: Uploading artifact (executable) - uses: actions/upload-artifact@v2 - with: - name: Update-Windows - path: export/release/windows/bin/CodenameEngine.exe + # - name: Uploading artifact (executable) + # uses: actions/upload-artifact@v2 + # with: + # name: Update-Windows + # path: export/release/windows/bin/CodenameEngine.exe - name: Clearing already existing cache uses: actions/github-script@v6 with: diff --git a/.vscode/settings.json b/.vscode/settings.json index 1d26a96f..fea6e023 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { - "editor.detectIndentation": false, - "editor.insertSpaces": false, + "editor.detectIndentation": false, + "editor.insertSpaces": false, "files.associations": { "*.hx": "haxe", "*.hscript": "haxe", diff --git a/FEATURES.md b/FEATURES.md index b2de03ca..9d53fcaf 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -5,7 +5,7 @@ _**QOL = Quality of Life**_

Player QOL Features

- + - New input system - New accuracy and misses system, added to fix the base game UI. - New options, including: @@ -72,7 +72,7 @@ _**QOL = Quality of Life**_ - Weeks are located in `data/weeks/weeks/` - If you need to rearrange the weeks in-game, you can use the `data/weeks/weeks.txt` file. - Editors for Charts and Characters (Stage coming soon) - - Undos/Redos supported + - Undos/Redos supported - Warning on closing unsaved work - Clean UI (for ocd freaks) - Mature Chart editor (Character editor rework soon) diff --git a/README.md b/README.md index d09c559a..604cd9c2 100644 --- a/README.md +++ b/README.md @@ -30,22 +30,10 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish ##### Tested on Windows 10 21H2 1. Install [version 4.2.5 of Haxe](https://haxe.org/download/version/4.2.5/). -2. Download [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe) -3. Wait for the Visual Studio Installer to install -4. On the Visual Studio installer screen, go to the "Individual components" tab and only select those options: - - MSVC v143 VS 2022 C++ x64/x86 build tools (Latest) - - Windows 10/11 SDK (any works) -5. This is what your Installation details panel should look like. Once correct, press "Install". - - ⚠ This will download around 1.07 GB of data from the internet, and will require around 5.5 GB of available space on your computer. - -

- -

- -6. Once you finish downloading those components, download and install [`git-scm`](https://git-scm.com/download/win). +2. Download and install [`git-scm`](https://git-scm.com/download/win). - Leave all installation options as default. -7. Run `update.bat` using cmd or double-clicking it, and wait for the libraries to install. -8. Once the libraries are installed, run `haxelib run lime test windows` to compile and launch the game (may take a long time) +3. Run `update.bat` using cmd or double-clicking it, and wait for the libraries to install. +4. Once the libraries are installed, run `haxelib run lime test windows` to compile and launch the game (may take a long time) - ℹ You can run `haxelib run lime setup` to make the lime command global, allowing you to execute `lime test windows` directly.
@@ -97,6 +85,5 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish - Credits to [Ne_Eo](https://twitter.com/Ne_Eo_Twitch) and the [3D-HaxeFlixel](https://github.com/lunarcleint/3D-HaxeFlixel) repository for Away3D Flixel support - Credits to the [FlxAnimate](https://github.com/Dot-Stuff/flxanimate) team for the Animate Atlas support - Credits to Smokey555 for the backup Animate Atlas to spritesheet code -- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) for video cutscene/mp4 support -- Credits to Aidan63 for [discord-rpc](https://github.com/Aidan63/linc_discord-rpc) for discord rpc integration +- Credits to MAJigsaw77 for [hxvlc](https://github.com/MAJigsaw77/hxvlc) (video cutscene/mp4 support) and [hxdiscord_rpc](https://github.com/MAJigsaw77/hxdiscord_rpc) (discord rpc integration)
diff --git a/art/readme.txt b/art/readme.txt index 99aa167e..0f924efd 100644 --- a/art/readme.txt +++ b/art/readme.txt @@ -1,30 +1,30 @@ - __ / -/ _ _ |_ |_ . _ -\__) (- |_ |_ | | ) - - __ -|_ _ _ _ | -| | (- (_| |( \/ - / - - _ _ _ -(_) | ) (_| - - __ -|_ _ . _| _ _ . _ |_ |_ _ _ |_ -| | | (_| (_| \/ | ) | (_) | ) |_ \/ (- (_| | ) - / _/ / + __ / +/ _ _ |_ |_ . _ +\__) (- |_ |_ | | ) + + __ +|_ _ _ _ | +| | (- (_| |( \/ + / + + _ _ _ +(_) | ) (_| + + __ +|_ _ . _| _ _ . _ |_ |_ _ _ |_ +| | | (_| (_| \/ | ) | (_) | ) |_ \/ (- (_| | ) + / _/ / HEY Thanks for downloadin Friday Night FUNKIN' If you downloaded this from any other place than ninja-muffin24.itch.io/FUNKIN -You might be in DANGER!!! +You might be in DANGER!!! The Itch.io release is the only official source for the desktop (PC, Mac, Linux) versions of the game! As of right now, the game is FREE! If you paid for it, you got SCAMMED! Now that that's out of the way.... -THANKS FOR DOWNLOADIN. +THANKS FOR DOWNLOADIN. #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#- PERMISSIONS XDDDD @@ -77,16 +77,16 @@ I love Tom Fulp. - Cameron ♪(´▽`) ################################################################################## - - _ _ _____ _ _ ____ ____ ___ _ _ _ _ ____ ____ -| \ | | | ____| | | | | / ___| | _ \ / _ \ | | | | | \ | | | _ \ / ___| -| \| | | _| | | /\ | | | | _ | |_) | | | | | | | | | | \| | | | | | \___ \ + + _ _ _____ _ _ ____ ____ ___ _ _ _ _ ____ ____ +| \ | | | ____| | | | | / ___| | _ \ / _ \ | | | | | \ | | | _ \ / ___| +| \| | | _| | | /\ | | | | _ | |_) | | | | | | | | | | \| | | | | | \___ \ | |\ | | |___ \ V V / | |_| | | _ < | |_| | | |_| | | |\ | | |_| | ___) | -|_| \_| |_____| \_/\_/ \____| |_| \_\ \___/ \___/ |_| \_| |____/ |____/ - _ _ _ ___ ___ __ _ _ _ _ _ -|_ | | |_ |_) \_/ | |_| | |\ | /__ |_ |_ | | |_ |_) \_/ / \ |\ | |_ +|_| \_| |_____| \_/\_/ \____| |_| \_\ \___/ \___/ |_| \_| |____/ |____/ + _ _ _ ___ ___ __ _ _ _ _ _ +|_ | | |_ |_) \_/ | |_| | |\ | /__ |_ |_ | | |_ |_) \_/ / \ |\ | |_ |_ \/ |_ | \ | | | | _|_ | \| \_| o |_)\/ |_ \/ |_ | \ | \_/ | \| |_ o - / / - + / / + ################################################################################## i stole this from stamper. \ No newline at end of file diff --git a/assets/data/characters/gf.xml b/assets/data/characters/gf.xml index 44c6f9e5..e9ac6776 100644 --- a/assets/data/characters/gf.xml +++ b/assets/data/characters/gf.xml @@ -7,7 +7,7 @@ - + diff --git a/assets/data/discord.hx b/assets/data/discord.hx new file mode 100644 index 00000000..9d5f0f6f --- /dev/null +++ b/assets/data/discord.hx @@ -0,0 +1,50 @@ +import funkin.backend.utils.DiscordUtil; + +function onGameOver() { + DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")"); +} + +function onDiscordPresenceUpdate(e) { + var data = e.presence; + + if(data.button1Label == null) + data.button1Label = "Codename Engine Discord"; + if(data.button1Url == null) + data.button1Url = "https://discord.gg/2NTCdsQvx4"; +} + +function onPlayStateUpdate() { + DiscordUtil.changeSongPresence( + PlayState.instance.detailsText, + (PlayState.instance.paused ? "Paused - " : "") + PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")", + PlayState.instance.inst, + PlayState.instance.getIconRPC() + ); +} + +function onMenuLoaded(name:String) { + // Name is either "Main Menu", "Freeplay", "Title Screen", "Options Menu", "Credits Menu", "Beta Warning", "Update Available Screen", "Update Screen" + DiscordUtil.changePresenceSince("In the Menus", null); +} + +function onEditorTreeLoaded(name:String) { + switch(name) { + case "Character Editor": + DiscordUtil.changePresenceSince("Choosing a Character", null); + case "Chart Editor": + DiscordUtil.changePresenceSince("Choosing a Chart", null); + case "Stage Editor": + DiscordUtil.changePresenceSince("Choosing a Stage", null); + } +} + +function onEditorLoaded(name:String, editingThing:String) { + switch(name) { + case "Character Editor": + DiscordUtil.changePresenceSince("Editing a Character", editingThing); + case "Chart Editor": + DiscordUtil.changePresenceSince("Editing a Chart", editingThing); + case "Stage Editor": + DiscordUtil.changePresenceSince("Editing a Stage", editingThing); + } +} \ No newline at end of file diff --git a/assets/data/global.hx b/assets/data/global.hx index 5e479c62..4c49ef69 100644 --- a/assets/data/global.hx +++ b/assets/data/global.hx @@ -1,3 +1,3 @@ // function update(elapsed:Float) - if (FlxG.keys.justPressed.F5) FlxG.resetState(); \ No newline at end of file + if (FlxG.keys.justPressed.F5) FlxG.resetState(); \ No newline at end of file diff --git a/assets/data/scripts/pixel.hx b/assets/data/scripts/pixel.hx index 1d14aa4e..cc8239cf 100644 --- a/assets/data/scripts/pixel.hx +++ b/assets/data/scripts/pixel.hx @@ -35,8 +35,8 @@ function onNoteCreation(event) { note.updateHitbox(); } -function onPostNoteCreation(event) { - var splashes = event.note; +function onPostNoteCreation(event) { + var splashes = event.note; if (pixelSplashes) splashes.splash = "pixel-default"; } diff --git a/assets/data/splashes/default.xml b/assets/data/splashes/default.xml index 352e4932..886588e3 100644 --- a/assets/data/splashes/default.xml +++ b/assets/data/splashes/default.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/splashes/pixel-default.xml b/assets/data/splashes/pixel-default.xml index 99a30bb3..97b8bf34 100644 --- a/assets/data/splashes/pixel-default.xml +++ b/assets/data/splashes/pixel-default.xml @@ -1,6 +1,6 @@ - + diff --git a/assets/data/splashes/pixel-secret.xml b/assets/data/splashes/pixel-secret.xml index 09fb5697..e710f2cf 100644 --- a/assets/data/splashes/pixel-secret.xml +++ b/assets/data/splashes/pixel-secret.xml @@ -1,6 +1,6 @@ - + diff --git a/assets/data/splashes/secret.xml b/assets/data/splashes/secret.xml index 31adb1f6..9122578b 100644 --- a/assets/data/splashes/secret.xml +++ b/assets/data/splashes/secret.xml @@ -1,5 +1,5 @@ - + diff --git a/assets/data/stages/limo.xml b/assets/data/stages/limo.xml index b223b357..a0dafd89 100644 --- a/assets/data/stages/limo.xml +++ b/assets/data/stages/limo.xml @@ -7,23 +7,23 @@ - + - + - + - + - + diff --git a/assets/data/stages/mall.xml b/assets/data/stages/mall.xml index 8da8a472..3552cf27 100644 --- a/assets/data/stages/mall.xml +++ b/assets/data/stages/mall.xml @@ -1,17 +1,17 @@ - + - + - + - + - + diff --git a/assets/data/stages/philly.hx b/assets/data/stages/philly.hx index 2a4b9780..989b29b5 100644 --- a/assets/data/stages/philly.hx +++ b/assets/data/stages/philly.hx @@ -90,7 +90,7 @@ function updateTrainPos():Void function trainReset():Void { - gf.playAnim('hairFall'); + gf.playAnim('hairFall', true); phillyTrain.x = FlxG.width + 200; trainMoving = false; // trainSound.stop(); diff --git a/assets/data/stages/school.xml b/assets/data/stages/school.xml index e4c4d5e1..dabf463b 100644 --- a/assets/data/stages/school.xml +++ b/assets/data/stages/school.xml @@ -23,7 +23,7 @@ antialiasing="false" scale="6" scroll="0.85" updateHitbox="true" type="loop"/> - + diff --git a/assets/data/stages/tank.xml b/assets/data/stages/tank.xml index 8ba6121f..d321e315 100644 --- a/assets/data/stages/tank.xml +++ b/assets/data/stages/tank.xml @@ -15,7 +15,7 @@ - + @@ -26,13 +26,13 @@ - + - - - - - - + + + + + + \ No newline at end of file diff --git a/assets/data/titlescreen/titlescreen.xml b/assets/data/titlescreen/titlescreen.xml index 0ff1053b..f16c8b62 100644 --- a/assets/data/titlescreen/titlescreen.xml +++ b/assets/data/titlescreen/titlescreen.xml @@ -1,8 +1,8 @@ - - + + diff --git a/assets/images/editors/autosave-delete.png b/assets/images/editors/autosave-delete.png new file mode 100644 index 00000000..2de6cbad Binary files /dev/null and b/assets/images/editors/autosave-delete.png differ diff --git a/assets/images/editors/autosave-icons.png b/assets/images/editors/autosave-icons.png new file mode 100644 index 00000000..03edceba Binary files /dev/null and b/assets/images/editors/autosave-icons.png differ diff --git a/assets/images/editors/charter/event-spr-add.xml b/assets/images/editors/charter/event-spr-add.xml index 92cd0504..5783f5c1 100644 --- a/assets/images/editors/charter/event-spr-add.xml +++ b/assets/images/editors/charter/event-spr-add.xml @@ -10,7 +10,7 @@ - + diff --git a/assets/images/editors/deleter circle.png b/assets/images/editors/deleter circle.png new file mode 100644 index 00000000..7d63d069 Binary files /dev/null and b/assets/images/editors/deleter circle.png differ diff --git a/assets/images/editors/deleter.png b/assets/images/editors/deleter.png new file mode 100644 index 00000000..8146955d Binary files /dev/null and b/assets/images/editors/deleter.png differ diff --git a/assets/images/editors/ui/context-icons.png b/assets/images/editors/ui/context-icons.png index 91976656..a9de6358 100644 Binary files a/assets/images/editors/ui/context-icons.png and b/assets/images/editors/ui/context-icons.png differ diff --git a/assets/images/editors/ui/topmenu.xml b/assets/images/editors/ui/topmenu.xml index 416300cf..433ab890 100644 --- a/assets/images/editors/ui/topmenu.xml +++ b/assets/images/editors/ui/topmenu.xml @@ -8,7 +8,7 @@ - + diff --git a/assets/shaders/chromaticAberration.frag b/assets/shaders/chromaticAberration.frag index aa1ca662..bd869adb 100644 --- a/assets/shaders/chromaticAberration.frag +++ b/assets/shaders/chromaticAberration.frag @@ -6,12 +6,12 @@ uniform vec2 blueOff; void main() { - vec2 uv = getCamPos(openfl_TextureCoordv); - vec4 col; - col.r = textureCam(bitmap, uv + redOff).r; - col.g = textureCam(bitmap, uv + greenOff).g; - col.b = textureCam(bitmap, uv + blueOff).b; - col.a = texture2D(bitmap, openfl_TextureCoordv).a; + vec2 uv = getCamPos(openfl_TextureCoordv); + vec4 col; + col.r = textureCam(bitmap, uv + redOff).r; + col.g = textureCam(bitmap, uv + greenOff).g; + col.b = textureCam(bitmap, uv + blueOff).b; + col.a = texture2D(bitmap, openfl_TextureCoordv).a; - gl_FragColor = col; + gl_FragColor = col; } \ No newline at end of file diff --git a/assets/shaders/coloredVignette.frag b/assets/shaders/coloredVignette.frag index a012e78f..531431d3 100644 --- a/assets/shaders/coloredVignette.frag +++ b/assets/shaders/coloredVignette.frag @@ -6,10 +6,10 @@ uniform float strength; void main() { vec2 uv = getCamPos(openfl_TextureCoordv); - vec3 col = pow(textureCam(bitmap, uv).rgb, vec3(1.0 / strength)); + vec3 col = pow(textureCam(bitmap, uv).rgb, vec3(1.0 / strength)); - float vignette = mix(1.0, 1.0 - amount, distance(uv, vec2(0.5))); - col = pow(mix(col * color, col, vignette), vec3(strength)); + float vignette = mix(1.0, 1.0 - amount, distance(uv, vec2(0.5))); + col = pow(mix(col * color, col, vignette), vec3(strength)); gl_FragColor = vec4(col, 1.0); } \ No newline at end of file diff --git a/assets/shaders/engine/circleProfilePicture.frag b/assets/shaders/engine/circleProfilePicture.frag index 6cf0b2ab..1a641250 100644 --- a/assets/shaders/engine/circleProfilePicture.frag +++ b/assets/shaders/engine/circleProfilePicture.frag @@ -6,6 +6,6 @@ void main() { float len = length(openfl_TextureCoordv - vec2(0.5, 0.5)); float r = 0.5 - (1.0 / openfl_TextureSize.x); vec4 c = flixel_texture2D(bitmap, openfl_TextureCoordv); - vec4 color = mix(vec4(0, 0, 0, 0.5), c, c.a); - gl_FragColor = color * clamp(1 - ((len - r) * openfl_TextureSize.x), 0, 1); + vec4 color = mix(vec4(0.0, 0.0, 0.0, 0.5), c, c.a); + gl_FragColor = color * clamp(1.0 - ((len - r) * openfl_TextureSize.x), 0.0, 1.0); } \ No newline at end of file diff --git a/assets/shaders/engine/colorPicker.frag b/assets/shaders/engine/colorPicker.frag index d31972d8..bc6c6e0c 100644 --- a/assets/shaders/engine/colorPicker.frag +++ b/assets/shaders/engine/colorPicker.frag @@ -3,9 +3,9 @@ // Used in colorwheel vec3 hsvToRgb(vec3 c) { //hsv.x = hue, hsv.y = saturation, hsv.z = value - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } uniform float hue; diff --git a/assets/shaders/engine/editorBlur.frag b/assets/shaders/engine/editorBlur.frag index 66b3eafb..ffcbc755 100644 --- a/assets/shaders/engine/editorBlur.frag +++ b/assets/shaders/engine/editorBlur.frag @@ -7,9 +7,9 @@ int steps = 16; int stepsInside = 2; float strength = 0.0075; vec4 getColor(vec2 pos) { - if (pos.x < 0) pos.x = 0; + if (pos.x < 0.0) pos.x = 0.0; else if (pos.x > 1.0 - (1.0 / openfl_TextureSize.x)) pos.x = 1.0 - (1.0 / openfl_TextureSize.x); - if (pos.y < 0) pos.y = 0; + if (pos.y < 0.0) pos.y = 0.0; else if (pos.y > 1.0 - (1.0 / openfl_TextureSize.y)) pos.y = 1.0 - (1.0 / openfl_TextureSize.y); // return textureCam(bitmap, pos); return flixel_texture2D(bitmap, pos); @@ -17,15 +17,18 @@ vec4 getColor(vec2 pos) { void main() { // vec2 camPos = getCamPos(openfl_TextureCoordv); vec2 camPos = openfl_TextureCoordv; - if (camPos.x < 0 || camPos.x > 1 || camPos.y < 0 || camPos.y > 1) + if (camPos.x < 0.0 || camPos.x > 1.0 || camPos.y < 0.0 || camPos.y > 1.0) return; vec4 color = getColor(camPos); float fsteps = steps; - for(float inside = 1; inside < stepsInside+1; inside++) { + for(float inside = 1.0; inside < stepsInside+1.0; inside++) { for(int i = 0; i < steps; i++) { float fi = i; - color += getColor(camPos + vec2(strength * (inside / stepsInside) * cos(fi / fsteps * (PI * 2.0)), strength * (inside / stepsInside) * sin(fi / fsteps * (PI * 2.0)))); + color += getColor(camPos + vec2( + strength * (inside / stepsInside) * cos(fi / fsteps * (PI * 2.0)), + strength * (inside / stepsInside) * sin(fi / fsteps * (PI * 2.0)) + )); } } diff --git a/assets/shaders/engine/editorWaveforms.frag b/assets/shaders/engine/editorWaveforms.frag new file mode 100644 index 00000000..c2ea827d --- /dev/null +++ b/assets/shaders/engine/editorWaveforms.frag @@ -0,0 +1,88 @@ +#pragma header + +// Used in charter by waveforms + +const vec3 gradient1 = vec3(114.0/255.0, 81.0/255.0, 135.0/255.0); +const vec3 gradient2 = vec3(144.0/255.0, 80.0/255.0, 186.0/255.0); + +const vec4 outlinecolor = vec4(0.1); +const vec3 lowdetailcol = vec3(0.0, 0.0, 1.0); + +uniform vec2 textureRes; +uniform float pixelOffset; + +uniform float playerPosition; + +uniform sampler2D waveformTexture; +uniform ivec2 waveformSize; + +uniform bool lowDetail; + +float getAmplitude(vec2 pixel) { + float pixelID = floor((pixel.y+pixelOffset)/3.0); + + // TODO: INVESTIGATE THE 1.+ AND WHY IT WORKS (SRSLY I GOT NO CLUE) -lunar + vec2 wavePixel = vec2(mod(pixelID,waveformSize.x), 1.0+floor(pixelID/waveformSize.x)); + vec4 waveData = texture2D(waveformTexture, wavePixel / waveformSize); + + int id = int(mod(wavePixel.x, 3.0)); + if(id == 0) return waveData.r; + if(id == 1) return waveData.g; + if(id == 2) return waveData.b; + return 0.0; +} + +float getAmpWidth(float amplitude) { + return (1.0-amplitude) * textureRes.x; +} + +bool inWaveForm(vec2 pixel, float width) { + float widthdiv2 = width/2.0; + return pixel.x > widthdiv2 && pixel.x < textureRes.x-widthdiv2; +} + +float getHightlight(float ampwidth) { + float remapCoord = (ampwidth/textureRes.x)/2.0; + float mappedCoord = map(openfl_TextureCoordv.x, remapCoord, 1.0-remapCoord, 0.0, 1.0); + return 1.0-(abs(mappedCoord-0.5)*2.0); +} + +void outlineWaveform(vec2 pixel, vec2 offset) { + float amplitude = getAmplitude(pixel - offset); + float ampwidth = (1.0-amplitude) * textureRes.x; + + if (!inWaveForm(pixel, ampwidth)) + gl_FragColor -= outlinecolor*mix(0.5, 0.8, amplitude); +} + +void highDetailWaveform(vec2 pixel, float amplitude, float ampwidth) { + vec3 gradientColor = mix(gradient1, gradient2, openfl_TextureCoordv.x); + gradientColor *= pixel.y+pixelOffset>playerPosition ? 0.7 : 1.0; + gradientColor = mix(gradientColor, gradientColor * vec3(1.8), map(getHightlight(amplitude), 0.0, 1.0, 0.5, 1.0)); + + float ampwidthHighlight = getAmpWidth(amplitude/3.0); + if (inWaveForm(pixel, ampwidthHighlight)) + gradientColor = mix(gradientColor, gradientColor * vec3(1.8), map(getHightlight(ampwidthHighlight), 0.0, 1.0, 0.5, 1.0)); + + gl_FragColor = vec4(vec3(gradientColor * mix(0.5, 0.8, amplitude))*openfl_Alphav, openfl_Alphav); + + outlineWaveform(pixel, vec2(0.0, -1.0)); + outlineWaveform(pixel, vec2(0.0, 1.0)); +} + +void lowDetailWaveform(vec2 pixel, float amplitude, float ampwidth) { + gl_FragColor = vec4(lowdetailcol * (pixel.y+pixelOffset>playerPosition ? 0.7 : 1.0), 1.0); +} + +void main() +{ + vec2 pixel = openfl_TextureCoordv * textureRes; + float amplitude = getAmplitude(pixel); + float ampwidth = getAmpWidth(amplitude); + + gl_FragColor = vec4(0.0); + if (!inWaveForm(pixel, ampwidth)) return; + + if (lowDetail) lowDetailWaveform(pixel, amplitude, ampwidth); + else highDetailWaveform(pixel, amplitude, ampwidth); +} \ No newline at end of file diff --git a/assets/shaders/engine/updaterShader.frag b/assets/shaders/engine/updaterShader.frag index f3a63166..ef6fec03 100644 --- a/assets/shaders/engine/updaterShader.frag +++ b/assets/shaders/engine/updaterShader.frag @@ -28,7 +28,7 @@ void main() { hsv.g = (1.0/3.0) * strength * coords.y; - hsv.r = mod(elapsed + coords.x + coords.y, 1); + hsv.r = mod(elapsed + coords.x + coords.y, 1.0); gl_FragColor = vec4(hsv2rgb(hsv), color.a); } \ No newline at end of file diff --git a/assets/songs/test/scripts/modchart.hx b/assets/songs/test/scripts/modchart.hx index de01830f..df37fb12 100644 --- a/assets/songs/test/scripts/modchart.hx +++ b/assets/songs/test/scripts/modchart.hx @@ -21,7 +21,7 @@ function postUpdate(elapsed) { } } - + // for(s in strumLines) { // for(i in 0...4) { // var n = s.members[i]; @@ -29,7 +29,7 @@ function postUpdate(elapsed) { // n.angle = 180; // } // } - - // if (curSection != null) - // defaultCamZoom = curSection.mustHitSection ? 0.9 : 0.5; + + // if (curSection != null) + // defaultCamZoom = curSection.mustHitSection ? 0.9 : 0.5; } \ No newline at end of file diff --git a/assets/songs/tutorial/charts/easy.json b/assets/songs/tutorial/charts/easy.json index adf1e086..49a8146f 100644 --- a/assets/songs/tutorial/charts/easy.json +++ b/assets/songs/tutorial/charts/easy.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":0,"type":1},{"params":[0],"time":9600,"type":1},{"params":[1],"time":14400,"type":1},{"params":[0],"time":19200,"type":1},{"params":[1],"time":24000,"type":1},{"params":[0],"time":28800,"type":1},{"params":[1],"time":33600,"type":1},{"params":[0],"time":38400,"type":1},{"params":[1],"time":43200,"type":1},{"params":[0],"time":48000,"type":1},{"params":[1],"time":50400,"type":1}],"strumLines":[{"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"position":"girlfriend","type":0,"characters":["gf"]},{"notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"position":"boyfriend","type":1,"characters":["bf"]}],"codenameChart":true,"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/charts/hard.json b/assets/songs/tutorial/charts/hard.json index 060a0b1f..c417029f 100644 --- a/assets/songs/tutorial/charts/hard.json +++ b/assets/songs/tutorial/charts/hard.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":0,"type":1},{"params":[0],"time":9600,"type":1},{"params":[1],"time":14400,"type":1},{"params":[0],"time":19200,"type":1},{"params":[1],"time":24000,"type":1},{"params":[0],"time":28800,"type":1},{"params":[1],"time":33600,"type":1},{"params":[0],"time":38400,"type":1},{"params":[1],"time":43200,"type":1},{"params":[0],"time":48000,"type":1},{"params":[1],"time":50400,"type":1}],"strumLines":[{"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"position":"girlfriend","type":0,"characters":["gf"]},{"notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":1,"sLen":0,"time":62550,"type":0},{"id":2,"sLen":0,"time":62700,"type":0},{"id":3,"sLen":0,"time":62850,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":1,"sLen":0,"time":63150,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":0,"time":63450,"type":0},{"id":2,"sLen":0,"time":63600,"type":0},{"id":3,"sLen":0,"time":63750,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":3,"sLen":0,"time":64050,"type":0},{"id":2,"sLen":0,"time":64200,"type":0},{"id":3,"sLen":0,"time":64350,"type":0},{"id":0,"sLen":0,"time":64650,"type":0},{"id":1,"sLen":0,"time":64500,"type":0}],"position":"boyfriend","type":1,"characters":["bf"]}],"codenameChart":true,"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0},{"id":0,"sLen":0,"time":62400,"type":0},{"id":1,"sLen":0,"time":62550,"type":0},{"id":2,"sLen":0,"time":62700,"type":0},{"id":3,"sLen":0,"time":62850,"type":0},{"id":2,"sLen":0,"time":63000,"type":0},{"id":1,"sLen":0,"time":63150,"type":0},{"id":0,"sLen":0,"time":63300,"type":0},{"id":1,"sLen":0,"time":63450,"type":0},{"id":2,"sLen":0,"time":63600,"type":0},{"id":3,"sLen":0,"time":63750,"type":0},{"id":2,"sLen":0,"time":63900,"type":0},{"id":3,"sLen":0,"time":64050,"type":0},{"id":2,"sLen":0,"time":64200,"type":0},{"id":3,"sLen":0,"time":64350,"type":0},{"id":1,"sLen":0,"time":64500,"type":0},{"id":0,"sLen":0,"time":64650,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/charts/normal.json b/assets/songs/tutorial/charts/normal.json index adf1e086..49a8146f 100644 --- a/assets/songs/tutorial/charts/normal.json +++ b/assets/songs/tutorial/charts/normal.json @@ -1 +1 @@ -{"events":[{"params":[1],"time":0,"type":1},{"params":[0],"time":9600,"type":1},{"params":[1],"time":14400,"type":1},{"params":[0],"time":19200,"type":1},{"params":[1],"time":24000,"type":1},{"params":[0],"time":28800,"type":1},{"params":[1],"time":33600,"type":1},{"params":[0],"time":38400,"type":1},{"params":[1],"time":43200,"type":1},{"params":[0],"time":48000,"type":1},{"params":[1],"time":50400,"type":1}],"strumLines":[{"notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"position":"girlfriend","type":0,"characters":["gf"]},{"notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"position":"boyfriend","type":1,"characters":["bf"]}],"codenameChart":true,"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file +{"codenameChart":true,"strumLines":[{"position":"girlfriend","notes":[{"id":0,"sLen":0,"time":9600,"type":0},{"id":3,"sLen":0,"time":10800,"type":0},{"id":0,"sLen":0,"time":12000,"type":0},{"id":3,"sLen":0,"time":13200,"type":0},{"id":2,"sLen":0,"time":19200,"type":0},{"id":1,"sLen":0,"time":20400,"type":0},{"id":2,"sLen":0,"time":21600,"type":0},{"id":1,"sLen":0,"time":22800,"type":0},{"id":0,"sLen":0,"time":28800,"type":0},{"id":2,"sLen":0,"time":30000,"type":0},{"id":1,"sLen":0,"time":31200,"type":0},{"id":3,"sLen":0,"time":32400,"type":0},{"id":1,"sLen":0,"time":38400,"type":0},{"id":1,"sLen":0,"time":39000,"type":0},{"id":2,"sLen":0,"time":39600,"type":0},{"id":1,"sLen":0,"time":40800,"type":0},{"id":1,"sLen":0,"time":41400,"type":0},{"id":3,"sLen":0,"time":42000,"type":0},{"id":1,"sLen":0,"time":48000,"type":0},{"id":2,"sLen":0,"time":48300,"type":0},{"id":3,"sLen":0,"time":48600,"type":0},{"id":2,"sLen":0,"time":48900,"type":0},{"id":3,"sLen":0,"time":49800,"type":0}],"visible":true,"type":0,"characters":["gf"]},{"position":"boyfriend","notes":[{"id":0,"sLen":0,"time":14400,"type":0},{"id":3,"sLen":0,"time":15600,"type":0},{"id":0,"sLen":0,"time":16800,"type":0},{"id":3,"sLen":0,"time":18000,"type":0},{"id":2,"sLen":0,"time":24000,"type":0},{"id":1,"sLen":0,"time":25200,"type":0},{"id":2,"sLen":0,"time":26400,"type":0},{"id":1,"sLen":0,"time":27600,"type":0},{"id":0,"sLen":0,"time":33600,"type":0},{"id":2,"sLen":0,"time":34800,"type":0},{"id":1,"sLen":0,"time":36000,"type":0},{"id":3,"sLen":0,"time":37200,"type":0},{"id":1,"sLen":0,"time":43200,"type":0},{"id":1,"sLen":0,"time":43800,"type":0},{"id":2,"sLen":0,"time":44400,"type":0},{"id":1,"sLen":0,"time":45600,"type":0},{"id":1,"sLen":0,"time":46200,"type":0},{"id":3,"sLen":0,"time":46800,"type":0},{"id":1,"sLen":0,"time":50400,"type":0},{"id":2,"sLen":0,"time":50700,"type":0},{"id":3,"sLen":0,"time":51000,"type":0},{"id":2,"sLen":0,"time":51300,"type":0},{"id":3,"sLen":0,"time":52200,"type":0},{"id":3,"sLen":0,"time":52800,"type":0},{"id":1,"sLen":0,"time":53400,"type":0},{"id":0,"sLen":0,"time":54000,"type":0},{"id":1,"sLen":0,"time":54600,"type":0},{"id":2,"sLen":0,"time":55200,"type":0},{"id":3,"sLen":0,"time":55800,"type":0},{"id":0,"sLen":0,"time":56400,"type":0},{"id":2,"sLen":0,"time":57000,"type":0},{"id":1,"sLen":750,"time":57600,"type":0}],"visible":true,"type":1,"characters":["bf"]}],"scrollSpeed":1,"noteTypes":[]} \ No newline at end of file diff --git a/assets/songs/tutorial/events.json b/assets/songs/tutorial/events.json new file mode 100644 index 00000000..a97b4b89 --- /dev/null +++ b/assets/songs/tutorial/events.json @@ -0,0 +1 @@ +{"events":[{"params":[1],"name":"Camera Movement","time":0},{"params":[0],"name":"Camera Movement","time":9600},{"params":[1],"name":"Camera Movement","time":14400},{"params":[0,"cheer"],"name":"Play Animation","time":18000},{"params":[1,"hey"],"name":"Play Animation","time":18600},{"params":[0],"name":"Camera Movement","time":19200},{"params":[1],"name":"Camera Movement","time":24000},{"params":[0,"cheer"],"time":27600,"name":"Play Animation"},{"params":[1,"hey"],"time":28200,"name":"Play Animation"},{"params":[0],"name":"Camera Movement","time":28800},{"params":[1],"name":"Camera Movement","time":33600},{"params":[0],"name":"Camera Movement","time":38400},{"params":[1],"name":"Camera Movement","time":43200},{"params":[0],"name":"Camera Movement","time":48000},{"params":[1],"name":"Camera Movement","time":50400}]} \ No newline at end of file diff --git a/assets/songs/tutorial/scripts/zoom.hx b/assets/songs/tutorial/scripts/zoom.hx new file mode 100644 index 00000000..0f7e37ac --- /dev/null +++ b/assets/songs/tutorial/scripts/zoom.hx @@ -0,0 +1,8 @@ +var lastFocused:Int = null; +var zoomin:FlxTween = null; + +function onNoteHit(event) + event.enableCamZooming = false; + +function onCameraMove(_) if(zoomin == null && lastFocused != (lastFocused = curCameraTarget)) + zoomin = FlxTween.tween(FlxG.camera, {zoom: curCameraTarget == 0 ? 1.3 : 1}, (Conductor.stepCrochet * 4 / 1000), {ease: FlxEase.elasticInOut, onComplete: function(_) zoomin = null}); \ No newline at end of file diff --git a/commandline/commands/Compiler.hx b/commandline/commands/Compiler.hx index 62000723..fe64c198 100644 --- a/commandline/commands/Compiler.hx +++ b/commandline/commands/Compiler.hx @@ -20,7 +20,7 @@ class Compiler { Sys.command("lime", arg); } - private static function getBuildTarget() { + public static function getBuildTarget() { return switch(Sys.systemName()) { case "Windows": "windows"; diff --git a/commandline/commands/Update.hx b/commandline/commands/Update.hx index d43c3620..6ac0f0f3 100644 --- a/commandline/commands/Update.hx +++ b/commandline/commands/Update.hx @@ -71,6 +71,20 @@ class Update { } } } + + // vswhere.exe its used to find any visual studio related installations on the system, including full visual studio ide installations, visual studio build tools installations, and other related components - Nex + if (Compiler.getBuildTarget().toLowerCase() == "windows" && new Process('"C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -property catalog_productDisplayVersion').exitCode(true) == 1) { + prettyPrint("Installing Microsoft Visual Studio Community (Dependency)"); + + // thanks to @crowplexus for these two lines! - Nex + Sys.command("curl -# -O https://download.visualstudio.microsoft.com/download/pr/3105fcfe-e771-41d6-9a1c-fc971e7d03a7/8eb13958dc429a6e6f7e0d6704d43a55f18d02a253608351b6bf6723ffdaf24e/vs_Community.exe"); + Sys.command("vs_Community.exe --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 --add Microsoft.VisualStudio.Component.Windows10SDK.19041 -p"); + + FileSystem.deleteFile("vs_Community.exe"); + prettyPrint('If it didn\'t say it before: Because of this component if you want to compile you have to restart the device.'); + Sys.print("Do you wish to do it now [y/n]? "); + if(Sys.stdin().readLine().toLowerCase() == "y") Sys.command("shutdown /r /t 0 /f"); + } } public static function prettyPrint(text:String) { diff --git a/libs.xml b/libs.xml index 50ebe3b9..cfdec718 100644 --- a/libs.xml +++ b/libs.xml @@ -1,8 +1,8 @@ - - + + @@ -10,13 +10,13 @@ - + - + - \ No newline at end of file + diff --git a/project.xml b/project.xml index 5d17fa17..77ae6af6 100644 --- a/project.xml +++ b/project.xml @@ -7,7 +7,7 @@ package="com.yoshman29.codenameengine" version="0.1.0" company="Yoshman29" /> - @@ -161,6 +161,13 @@ + + + + + + + @@ -170,11 +177,9 @@ - + - -
diff --git a/source/funkin/backend/FunkinSprite.hx b/source/funkin/backend/FunkinSprite.hx index ec8183ce..51a322c0 100644 --- a/source/funkin/backend/FunkinSprite.hx +++ b/source/funkin/backend/FunkinSprite.hx @@ -41,7 +41,10 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I public var debugMode:Bool = false; public var animDatas:Map = []; - public var beatInterval:Int = 1; + /** + * ODD interval -> asynced; EVEN interval -> synced + */ + public var beatInterval(default, set):Int = 2; public var beatOffset:Int = 0; public var skipNegativeBeats:Bool = false; @@ -358,4 +361,13 @@ class FunkinSprite extends FlxSkewedSprite implements IBeatReceiver implements I return animateAtlas != null ? (animateAtlas.anim.finished) : (animation.curAnim != null ? animation.curAnim.finished : true); } #end + + // Getter / Setters + + @:noCompletion private function set_beatInterval(v:Int) { + if (v < 1) + v = 1; + + return beatInterval = v; + } } diff --git a/source/funkin/backend/MusicBeatTransition.hx b/source/funkin/backend/MusicBeatTransition.hx index 6313c10b..34b02388 100644 --- a/source/funkin/backend/MusicBeatTransition.hx +++ b/source/funkin/backend/MusicBeatTransition.hx @@ -53,7 +53,7 @@ class MusicBeatTransition extends MusicBeatSubstate { finish(); } }); - + super.create(); } diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx index a88377ad..cd1704ed 100644 --- a/source/funkin/backend/assets/ModsFolder.hx +++ b/source/funkin/backend/assets/ModsFolder.hx @@ -51,10 +51,11 @@ class ModsFolder { */ private static var __firstTime:Bool = true; /** - * Initialises `mods` folder by adding callbacks and such. + * Initialises `mods` folder. */ public static function init() { - + if(!getModsList().contains(Options.lastLoadedMod)) + Options.lastLoadedMod = null; } /** @@ -89,6 +90,24 @@ class ModsFolder { #end } + public static function getModsList():Array { + var mods:Array = []; + #if MOD_SUPPORT + for(modFolder in FileSystem.readDirectory(modsPath)) { + if (FileSystem.isDirectory('${modsPath}${modFolder}')) { + mods.push(modFolder); + } else { + var ext = Path.extension(modFolder).toLowerCase(); + switch(ext) { + case 'zip': + // is a zip mod!! + mods.push(Path.withoutExtension(modFolder)); + } + } + } + #end + return mods; + } public static function getLoadedMods():Array { var libs = []; for (i in Paths.assetsTree.libraries) { diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 00aae99d..d8c6ba39 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -100,7 +100,7 @@ class Paths } return getPath('images/$key.$ext', library); } - + inline static public function script(key:String, ?library:String, isAssetsPath:Bool = false) { var scriptPath = isAssetsPath ? key : getPath(key, library); if (!OpenFlAssets.exists(scriptPath)) { diff --git a/source/funkin/backend/chart/Chart.hx b/source/funkin/backend/chart/Chart.hx index d18640bb..a1376e82 100644 --- a/source/funkin/backend/chart/Chart.hx +++ b/source/funkin/backend/chart/Chart.hx @@ -1,5 +1,6 @@ package funkin.backend.chart; +import funkin.backend.system.Conductor; import funkin.backend.chart.ChartData; import flixel.util.FlxColor; import haxe.io.Path; @@ -143,7 +144,7 @@ class Chart { base = data; } else { // base game chart - BaseGameParser.parse(data, base); + FNFLegacyParser.parse(data, base); } #end } @@ -200,17 +201,19 @@ class Chart { var meta = filteredChart.meta; #if sys - if (!FileSystem.exists('${songFolderPath}/charts/')) - FileSystem.createDirectory('${songFolderPath}/charts/'); + var saveFolder:String = saveSettings.folder == null ? "charts" : saveSettings.folder; - var chartPath = '${songFolderPath}/charts/${difficulty.trim()}.json'; + if (!FileSystem.exists('${songFolderPath}/$saveFolder/')) + FileSystem.createDirectory('${songFolderPath}/$saveFolder/'); + + var chartPath = '${songFolderPath}/$saveFolder/${difficulty.trim()}.json'; var metaPath = '${songFolderPath}/meta.json'; - File.saveContent(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? "\t" : null)); + CoolUtil.safeSaveFile(chartPath, Json.stringify(filteredChart, null, saveSettings.prettyPrint == true ? "\t" : null)); // idk how null reacts to it so better be sure if (saveSettings.overrideExistingMeta == true || !FileSystem.exists(metaPath)) - File.saveContent(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? "\t" : null)); + CoolUtil.safeSaveFile(metaPath, Json.stringify(meta, null, saveSettings.prettyPrint == true ? "\t" : null)); #end return filteredChart; } @@ -242,4 +245,5 @@ typedef ChartSaveSettings = { var ?saveMetaInChart:Bool; var ?saveEventsInChart:Bool; var ?prettyPrint:Bool; + var ?folder:String; } diff --git a/source/funkin/backend/chart/ChartData.hx b/source/funkin/backend/chart/ChartData.hx index fc293475..c46164f5 100644 --- a/source/funkin/backend/chart/ChartData.hx +++ b/source/funkin/backend/chart/ChartData.hx @@ -59,8 +59,7 @@ typedef ChartEvent = { var params:Array; } -@:enum -abstract ChartStrumLineType(Int) from Int to Int { +enum abstract ChartStrumLineType(Int) from Int to Int { /** * STRUMLINE IS MARKED AS OPPONENT - WILL BE PLAYED BY CPU, OR PLAYED BY PLAYER IF OPPONENT MODE IS ON */ diff --git a/source/funkin/backend/chart/EventsData.hx b/source/funkin/backend/chart/EventsData.hx index 36f20a2e..7c105d22 100644 --- a/source/funkin/backend/chart/EventsData.hx +++ b/source/funkin/backend/chart/EventsData.hx @@ -13,7 +13,7 @@ class EventsData { public static var defaultEventsList:Array = ["HScript Call", "Camera Movement", "Add Camera Zoom", "Camera Modulo Change", "Camera Flash", "BPM Change", "Scroll Speed Change", "Alt Animation Toggle", "Play Animation"]; public static var defaultEventsParams:Map> = [ "HScript Call" => [ - {name: "Function Name", type: TString, defValue: "myFunc"}, + {name: "Function Name", type: TString, defValue: "myFunc"}, {name: "Function Parameters (String split with commas)", type: TString, defValue: ""} ], "Camera Movement" => [{name: "Camera Target", type: TStrumLine, defValue: 0}], diff --git a/source/funkin/backend/chart/BaseGameParser.hx b/source/funkin/backend/chart/FNFLegacyParser.hx similarity index 51% rename from source/funkin/backend/chart/BaseGameParser.hx rename to source/funkin/backend/chart/FNFLegacyParser.hx index 9d017621..ddf1588c 100644 --- a/source/funkin/backend/chart/BaseGameParser.hx +++ b/source/funkin/backend/chart/FNFLegacyParser.hx @@ -1,6 +1,9 @@ package funkin.backend.chart; -class BaseGameParser { +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.system.Conductor; + +class FNFLegacyParser { public static function parse(data:Dynamic, result:ChartData) { // base fnf chart parsing var data:SwagSong = data; @@ -109,7 +112,95 @@ class BaseGameParser { curTime += curCrochet * beatsPerMeasure; } } + + // have conductor set up BEFORE you run this :D -lunar + public static function encode(chart:ChartData):Dynamic { + var base:SwagSong = __convertToSwagSong(chart); + base.notes = __convertToSwagSections(chart); + + for (strumLine in chart.strumLines) + for (note in strumLine.notes) { + var section:Int = Math.floor(Conductor.getStepForTime(note.time) / Conductor.getMeasureLength()); + var swagSection:SwagSection = base.notes[section]; + if (section > 0 && section < base.notes.length) { + var sectionNote:Array = [ + note.time, // TIME + note.id, // DATA + note.sLen // SUSTAIN LENGTH + ]; + + if ((swagSection.mustHitSection && strumLine.type == OPPONENT) || + (!swagSection.mustHitSection && strumLine.type == PLAYER)) + sectionNote[1] += 4; + swagSection.sectionNotes.push(note); + } + } + + return {song: base}; + } + + // To make it easier to write the psych parser... -lunar + @:noCompletion public static function __convertToSwagSong(chart:ChartData):SwagSong { + var base:SwagSong = { + song: chart.meta.name, + notes: null, + bpm: chart.meta.bpm, + needsVoices: chart.meta.needsVoices, + speed: chart.scrollSpeed, + + player1: null, + player2: null, + validScore: true + }; + + for (strumLine in chart.strumLines) + switch (strumLine.type) { + case OPPONENT: + if (base.player2 == null) base.player2 = strumLine.characters.getDefault(["dad"])[0]; + case PLAYER: + if (base.player1 == null) base.player1 = strumLine.characters.getDefault(["bf"])[0]; + case ADDITIONAL: // do nothing + } + + return base; + } + + @:noCompletion public static function __convertToSwagSections(chart:ChartData):Array { + var events:Array = [for (event in chart.events) Reflect.copy(event)]; + + var measures:Float = Conductor.getMeasuresLength(); + var sections:Int = Math.floor(measures) + (measures % 1 > 0 ? 1 : 0); + + var notes:Array = cast new haxe.ds.Vector(sections); + for (section in 0...sections) { + var baseSection:SwagSection = { + sectionNotes: [], + lengthInSteps: Std.int(Conductor.getMeasureLength()), + mustHitSection: notes[section-1] != null ? notes[section-1].mustHitSection : false, + bpm: notes[section-1] != null ? notes[section-1].bpm : chart.meta.bpm, + changeBPM: false, + altAnim: notes[section-1] != null ? notes[section-1].altAnim : false + }; + + var sectionEndTime:Float = Conductor.getTimeForStep(Conductor.getMeasureLength() * (section+1)); + while(events.length > 0 && events[0].time < sectionEndTime) { + var event:ChartEvent = events.shift(); + switch (event.name) { + case "Camera Movement": + baseSection.mustHitSection = chart.strumLines[event.params[0]].type == PLAYER; + case "Alt Animation Toggle": + baseSection.altAnim = event.params[0] || event.params[1]; + case "BPM Change": + baseSection.changeBPM = true; + baseSection.bpm = event.params[0]; + } + } + notes[section] = baseSection; + } + return notes; + } } + typedef SwagSong = { var song:String; @@ -117,13 +208,14 @@ typedef SwagSong = var bpm:Float; var needsVoices:Bool; var speed:Float; - var stage:String; - var noteTypes:Array; + var ?stage:String; + var ?noteTypes:Array; + var ?events:Array; var player1:String; var player2:String; - var gf:String; - var gfVersion:String; + var ?gf:String; + var ?gfVersion:String; var validScore:Bool; // ADDITIONAL STUFF THAT MAY NOT BE PRESENT IN CHART @@ -136,9 +228,12 @@ typedef SwagSection = { var sectionNotes:Array; var lengthInSteps:Int; + var ?sectionBeats:Float; + var ?typeOfSection:Int; var mustHitSection:Bool; + var ?gfSection:Bool; var bpm:Float; var changeBPM:Bool; var altAnim:Null; - @:optional var camTarget:Null; + var ?camTarget:Null; } diff --git a/source/funkin/backend/chart/PsychParser.hx b/source/funkin/backend/chart/PsychParser.hx new file mode 100644 index 00000000..1af9962a --- /dev/null +++ b/source/funkin/backend/chart/PsychParser.hx @@ -0,0 +1,116 @@ +package funkin.backend.chart; + +import funkin.backend.chart.FNFLegacyParser.SwagSection; +import funkin.backend.chart.ChartData.ChartEvent; +import funkin.backend.chart.FNFLegacyParser.SwagSong; +import funkin.backend.system.Conductor; + +class PsychParser { + // Alreadly parsed in sections + public static var ignoreEvents:Array = [ + "Camera Movement", + "Alt Animation Toggle", + "BPM Change" + ]; + + public static function parse(data:Dynamic, result:ChartData) + FNFLegacyParser.parse(data, result); + + public static function encode(chart:ChartData):Dynamic { + var base:SwagSong = FNFLegacyParser.__convertToSwagSong(chart); + base.notes = FNFLegacyParser.__convertToSwagSections(chart); + base.stage = chart.stage; + + for (section in base.notes) + section.sectionBeats = Conductor.beatsPerMeasure; + + for (strumLine in chart.strumLines) + if (strumLine.type == ADDITIONAL && base.gfVersion == null) + base.gfVersion = strumLine.characters.getDefault(["gf"])[0]; + + for (strumLine in chart.strumLines) + for (note in strumLine.notes) { + var section:Int = Math.floor(Conductor.getStepForTime(note.time) / Conductor.getMeasureLength()); + var swagSection:SwagSection = base.notes[section]; + + if (section >= 0 && section < base.notes.length) { + var sectionNote:Array = [ + note.time, // TIME + note.id, // DATA + note.sLen, // SUSTAIN LENGTH + chart.noteTypes.getDefault([""])[note.type] // NOTE TYPE + ]; + + if ((swagSection.mustHitSection && strumLine.type == OPPONENT) || + (!swagSection.mustHitSection && strumLine.type == PLAYER)) + sectionNote[1] += 4; + swagSection.sectionNotes.push(sectionNote); + } + } + + var groupedEvents:Array> = []; + var __last:Array = null; + var __lastTime:Float = Math.NaN; + for (e in [for (event in chart.events) Reflect.copy(event)]) { + if (e == null || ignoreEvents.contains(e.name)) continue; + + if (__last != null && __lastTime == e.time) + __last.push(e); + else { + __last = [e]; + __lastTime = e.time; + groupedEvents.push(__last); + } + } + + base.events = []; + for (events in groupedEvents) { + var psychEvents:Array = []; + for (event in events) + switch (event.name) { + case "Add Camera Zoom": + psychEvents.push([ + event.name, + event.params[1] == "camGame" ? event.params[0] : 0, // CAMERA ZOOM + event.params[1] == "camHUD" ? event.params[0] : 0, // UI ZOOM + ]); + case "Play Animation": + psychEvents.push([ + event.name, + event.params[1], // ANIMATION TO PLAY + switch (chart.strumLines[event.params[0]].type) { // CHARACTER + case PLAYER: "bf"; + case OPPONENT: "dad"; + case ADDITIONAL: "gf"; + } + ]); + case "Scroll Speed Change": + var eventStep:Float = Conductor.getStepForTime(event.time); + psychEvents.push([ + "Change Scroll Speed", + FlxMath.roundDecimal(event.params[1]/chart.scrollSpeed, 2), // SCROLL SPEED MULTIPLER + FlxMath.roundDecimal( // TIME + event.params[0] ? // IS TWEENED? + (Conductor.getTimeForStep(eventStep+event.params[2]) - Conductor.getTimeForStep(eventStep))/1000 + : 0, 2) + ]); + default: + // TODO: allow custom formats in event.json + var split:Float = event.params.length / 2; + + var val1:String = [for (arg in 0...Math.ceil(split)) event.params[arg]].join(","); + var val2:String = [for (arg in Math.ceil(split)...event.params.length) event.params[arg]].join(","); + + psychEvents.push([event.name, val1, val2]); + } + + for (psychEvent in psychEvents) + for (i in 1...3) // Turn both vals into strings + if (!(psychEvent[i] is String)) + psychEvent[i] = Std.string(psychEvent[i]); + + base.events.push([events[0].time, psychEvents]); + } + return {song: base}; + } +} diff --git a/source/funkin/backend/scripting/DummyScript.hx b/source/funkin/backend/scripting/DummyScript.hx index 24ef2194..75080b45 100644 --- a/source/funkin/backend/scripting/DummyScript.hx +++ b/source/funkin/backend/scripting/DummyScript.hx @@ -5,7 +5,7 @@ package funkin.backend.scripting; */ class DummyScript extends Script { public var variables:Map = []; - + public override function get(v:String) {return variables.get(v);} public override function set(v:String, v2:Dynamic) {return variables.set(v, v2);} public override function onCall(method:String, parameters:Array):Dynamic { diff --git a/source/funkin/backend/scripting/GlobalScript.hx b/source/funkin/backend/scripting/GlobalScript.hx index 34da69b5..5c037aec 100644 --- a/source/funkin/backend/scripting/GlobalScript.hx +++ b/source/funkin/backend/scripting/GlobalScript.hx @@ -79,12 +79,14 @@ class GlobalScript { } public static function event(name:String, event:T):T { - if (scripts != null) scripts.call(name, [event]); + if (scripts != null) + scripts.event(name, event); return event; } public static function call(name:String, ?args:Array) { - if (scripts != null) scripts.call(name, args); + if (scripts != null) + scripts.call(name, args); } public static function onModSwitch(newMod:String) { call("destroy"); @@ -93,7 +95,8 @@ class GlobalScript { for (i in funkin.backend.assets.ModsFolder.getLoadedMods()) { var path = Paths.script('data/global/LIB_$i'); var script = Script.create(path); - if (script is DummyScript) continue; + if (script is DummyScript) + continue; script.remappedNames.set(script.fileName, '$i:${script.fileName}'); scripts.add(script); script.load(); diff --git a/source/funkin/backend/scripting/events/DiscordPresenceUpdateEvent.hx b/source/funkin/backend/scripting/events/DiscordPresenceUpdateEvent.hx index 782d70a0..c17cbb68 100644 --- a/source/funkin/backend/scripting/events/DiscordPresenceUpdateEvent.hx +++ b/source/funkin/backend/scripting/events/DiscordPresenceUpdateEvent.hx @@ -1,12 +1,8 @@ package funkin.backend.scripting.events; -#if DISCORD_RPC -import discord_rpc.DiscordRpc.DiscordPresenceOptions; -#end - final class DiscordPresenceUpdateEvent extends CancellableEvent { /** * Object containing all of the data for the presence. Can be altered. */ - public var presenceData:#if DISCORD_RPC DiscordPresenceOptions #else Dynamic #end; + public var presence:funkin.backend.utils.DiscordUtil.DPresence; } \ No newline at end of file diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index 5a63d804..ef91d128 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -370,15 +370,18 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { var field = Reflect.field(data, name); var cl = Type.getClassName(Type.getClass(field)); - // cant do "field is ShaderInput" for some reason - if (cl.startsWith("openfl.display.ShaderParameter")) { - var sp = cast(field, ShaderParameter); - @:privateAccess - return (sp.__length > 1) ? sp.value : sp.value[0]; - } else if (cl.startsWith("openfl.display.ShaderInput")) { - var si = cast(field, ShaderInput); - return si.input; - } + // little problem we are facing boys... + + // cant do "field is ShaderInput" because ShaderInput has the @:generic metadata + // aka instead of ShaderInput it gets built as ShaderInput_Float + // this should be fine tho because we check the class, and the fields dont vary based on the type + + // thanks for looking in the code cne fans :D!! -lunar + + if (cl.startsWith("openfl.display.ShaderParameter")) + return (field.__length > 1) ? field.value : field.value[0]; + else if (cl.startsWith("openfl.display.ShaderInput")) + return field.input; return field; } diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx index 87bd26d8..cad2266e 100644 --- a/source/funkin/backend/system/Main.hx +++ b/source/funkin/backend/system/Main.hx @@ -120,8 +120,6 @@ class Main extends Sprite ShaderResizeFix.init(); Logs.init(); Paths.init(); - ModsFolder.init(); - DiscordUtil.init(); #if GLOBAL_SCRIPT funkin.backend.scripting.GlobalScript.init(); #end @@ -159,6 +157,7 @@ class Main extends Sprite FlxG.mouse.useSystemCursor = true; + ModsFolder.init(); #if MOD_SUPPORT ModsFolder.switchMod(modToLoad.getDefault(Options.lastLoadedMod)); #end diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index 94b1a2d3..885da7b0 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -41,7 +41,7 @@ class MainState extends FlxState { else if (addon.startsWith("[HIGH]")) _highPriorityAddons.insert(0, addon); else _noPriorityAddons.insert(0, addon); } - for (addon in _lowPriorityAddons) + for (addon in _lowPriorityAddons) Paths.assetsTree.addLibrary(ModsFolder.loadModLib('${ModsFolder.addonsPath}$addon', StringTools.ltrim(addon.substr("[LOW]".length)))); } if (ModsFolder.currentModFolder != null) @@ -55,7 +55,7 @@ class MainState extends FlxState { Main.refreshAssets(); ModsFolder.onModSwitch.dispatch(ModsFolder.currentModFolder); - DiscordUtil.reloadJsonData(); + DiscordUtil.init(); EventsData.reloadEvents(); TitleState.initialized = false; @@ -68,7 +68,7 @@ class MainState extends FlxState { #if sys sys.FileSystem.createDirectory('./.temp/'); - #if windows Sys.command("attrib +h .temp"); #end + #if windows new funkin.backend.utils.native.HiddenProcess("attrib +h .temp"); #end #end } } \ No newline at end of file diff --git a/source/funkin/backend/system/framerate/FlixelInfo.hx b/source/funkin/backend/system/framerate/FlixelInfo.hx index 5535c962..259b7f8f 100644 --- a/source/funkin/backend/system/framerate/FlixelInfo.hx +++ b/source/funkin/backend/system/framerate/FlixelInfo.hx @@ -13,7 +13,7 @@ class FlixelInfo extends FramerateCategory { var c:Int = 0; for(_ in FlxG.bitmap._cache.keys()) c++; - + _text = 'State: ${Type.getClassName(Type.getClass(FlxG.state))}'; _text += '\nObject Count: ${FlxG.state.members.length}'; _text += '\nCamera Count: ${FlxG.cameras.list.length}'; @@ -27,7 +27,7 @@ class FlixelInfo extends FramerateCategory { _text += '\nPoint Count: ${points._count}'; #end } - + this.text.text = _text; super.__enterFrame(t); } diff --git a/source/funkin/backend/system/framerate/MemoryCounter.hx b/source/funkin/backend/system/framerate/MemoryCounter.hx index 863bdb8c..01321f0d 100644 --- a/source/funkin/backend/system/framerate/MemoryCounter.hx +++ b/source/funkin/backend/system/framerate/MemoryCounter.hx @@ -7,7 +7,7 @@ import openfl.text.TextField; class MemoryCounter extends Sprite { public var memoryText:TextField; public var memoryPeakText:TextField; - + public var memory:Float = 0; public var memoryPeak:Float = 0; diff --git a/source/funkin/backend/system/framerate/StatsInfo.hx b/source/funkin/backend/system/framerate/StatsInfo.hx index 77a9cc9e..937bdb10 100644 --- a/source/funkin/backend/system/framerate/StatsInfo.hx +++ b/source/funkin/backend/system/framerate/StatsInfo.hx @@ -14,7 +14,7 @@ class StatsInfo extends FramerateCategory { _text = "totalDC: " + Context3DStats.totalDrawCalls(); _text += "\nstageDC: " + Context3DStats.contextDrawCalls(DrawCallContext.STAGE); _text += "\nstage3DDC: " + Context3DStats.contextDrawCalls(DrawCallContext.STAGE3D); - + this.text.text = _text; super.__enterFrame(t); } diff --git a/source/funkin/backend/system/github/GitHub.hx b/source/funkin/backend/system/github/GitHub.hx index eeb535c5..27ba825b 100644 --- a/source/funkin/backend/system/github/GitHub.hx +++ b/source/funkin/backend/system/github/GitHub.hx @@ -3,7 +3,6 @@ package funkin.backend.system.github; #if GITHUB_API import haxe.Json; import haxe.Exception; -import haxe.Http; #end // TODO: Document further and perhaps make this a Haxelib. @@ -18,7 +17,7 @@ class GitHub { public static function getReleases(user:String, repository:String, ?onError:Exception->Void):Array { #if GITHUB_API try { - var data = Json.parse(__requestOnGitHubServers('https://api.github.com/repos/${user}/${repository}/releases')); + var data = Json.parse(HttpUtil.requestText('https://api.github.com/repos/${user}/${repository}/releases')); if (!(data is Array)) throw __parseGitHubException(data); @@ -41,7 +40,7 @@ class GitHub { public static function getContributors(user:String, repository:String, ?onError:Exception->Void):Array { #if GITHUB_API try { - var data = Json.parse(__requestOnGitHubServers('https://api.github.com/repos/${user}/${repository}/contributors')); + var data = Json.parse(HttpUtil.requestText('https://api.github.com/repos/${user}/${repository}/contributors')); if (!(data is Array)) throw __parseGitHubException(data); @@ -63,7 +62,7 @@ class GitHub { public static function getOrganization(org:String, ?onError:Exception->Void):GitHubOrganization { #if GITHUB_API try { - var data = Json.parse(__requestOnGitHubServers('https://api.github.com/orgs/$org')); + var data = Json.parse(HttpUtil.requestText('https://api.github.com/orgs/$org')); if (Reflect.hasField(data, "documentation_url")) throw __parseGitHubException(data); @@ -86,7 +85,7 @@ class GitHub { public static function getOrganizationMembers(org:String, ?onError:Exception->Void):Array { #if GITHUB_API try { - var data = Json.parse(__requestOnGitHubServers('https://api.github.com/orgs/$org/members')); + var data = Json.parse(HttpUtil.requestText('https://api.github.com/orgs/$org/members')); if (Reflect.hasField(data, "documentation_url")) throw __parseGitHubException(data); @@ -111,7 +110,7 @@ class GitHub { try { var url = 'https://api.github.com/users/$user'; - var data = Json.parse(__requestOnGitHubServers(url)); + var data = Json.parse(HttpUtil.requestText(url)); if (Reflect.hasField(data, "documentation_url")) throw __parseGitHubException(data); @@ -134,59 +133,6 @@ class GitHub { public static inline function filterReleases(releases:Array, keepPrereleases:Bool = true, keepDrafts:Bool = false) return #if GITHUB_API [for(release in releases) if (release != null && (!release.prerelease || (release.prerelease && keepPrereleases)) && (!release.draft || (release.draft && keepDrafts))) release] #else releases #end; - public static function __requestOnGitHubServers(url:String) { - var r = null; - #if GITHUB_API - var h = new Http(url); - h.setHeader("User-Agent", "request"); - - h.onStatus = function(s) { - if(isRedirect(s)) - r = __requestOnGitHubServers(h.responseHeaders.get("Location")); - }; - - h.onData = function(d) { - if(r == null) r = d; - } - h.onError = function(e) { - throw e; - } - - h.request(false); - #end - return r; - } - public static function __requestBytesOnGitHubServers(url:String) { - var r = null; - #if GITHUB_API - var h = new Http(url); - h.setHeader("User-Agent", "request"); - - h.onStatus = function(s) { - if(isRedirect(s)) - r = __requestBytesOnGitHubServers(h.responseHeaders.get("Location")); - }; - - h.onBytes = function(d) { - if(r == null) r = d; - } - h.onError = function(e) { - throw e; - } - - h.request(false); - #end - return r; - } - private static function isRedirect(status:Int):Bool { - switch (status) { - // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex - case 301 | 302 | 307 | 308 : - trace("Redirected with status code: " + status); - return true; - } - return false; - } private static function __parseGitHubException(obj:Dynamic):GitHubException { #if GITHUB_API var msg:String = "(No message)"; diff --git a/source/funkin/backend/system/macros/Utils.hx b/source/funkin/backend/system/macros/Utils.hx index df25030c..1d4873f4 100644 --- a/source/funkin/backend/system/macros/Utils.hx +++ b/source/funkin/backend/system/macros/Utils.hx @@ -37,4 +37,21 @@ class Utils { return expr; #end } + + public static macro function safeSet(variable:Null, value:Null):Null { + #if macro + return macro if (${value} != null) ${variable} = ${value}; + #end + } + public static macro function safeSetWrapper(variable:Null, value:Null, wrapper:Null):Null { + #if macro + return macro if (${value} != null) ${variable} = ${wrapper}(${value}); + #end + } + + public static macro function safeReflection(variable:Null, value:Null, field:Null):Null { + #if macro + return macro if (Reflect.hasField(${value}, ${field})) ${variable} = Reflect.field(${value}, ${field}); + #end + } } \ No newline at end of file diff --git a/source/funkin/backend/system/updating/AsyncUpdater.hx b/source/funkin/backend/system/updating/AsyncUpdater.hx index 52b59766..af7cfff5 100644 --- a/source/funkin/backend/system/updating/AsyncUpdater.hx +++ b/source/funkin/backend/system/updating/AsyncUpdater.hx @@ -21,14 +21,14 @@ class AsyncUpdater { public function new(releases:Array) { this.releases = releases; } - + public function execute() { Main.execAsync(installUpdates); } #end - - - #if windows + + + #if windows public static var executableGitHubName:String = "update-windows.exe"; public static var executableName:String = "CodenameEngine.exe"; #end @@ -49,7 +49,7 @@ class AsyncUpdater { public var lastTime:Float = 0; public var oldBytesLoaded:Float = 0; - + public function installUpdates() { prepareInstallationEnvironment(); downloadFiles(); @@ -64,7 +64,7 @@ class AsyncUpdater { progress.curFileName = e; trace('extracting file ${path}'); var reader = ZipUtil.openZip(path); - + progress.curZipProgress = new ZipProgress(); ZipUtil.uncompressZip(reader, './', null, progress.curZipProgress); // FileSystem.deleteFile(path); @@ -72,7 +72,7 @@ class AsyncUpdater { if (executableReplaced = FileSystem.exists('$path$executableName')) { progress.curFile = files.length; progress.curFileName = executableName; - + var progPath = Sys.programPath(); var bakFile = '${Path.withoutExtension(progPath)}.bak'; if (FileSystem.exists(bakFile)) @@ -133,7 +133,7 @@ class AsyncUpdater { downloadStream.addEventListener(ProgressEvent.PROGRESS, function(e) { progress.bytesLoaded = e.bytesLoaded; progress.bytesTotal = e.bytesTotal; - + var curTime = Lib.getTimer(); progress.downloadSpeed = (e.bytesLoaded - oldBytesLoaded) / ((curTime - lastTime) / 1000); @@ -160,7 +160,7 @@ class AsyncUpdater { public function prepareInstallationEnvironment() { progress.step = PREPARING; - + #if windows path = '${Sys.getEnv("TEMP")}\\Codename Engine\\Updater\\'; #else diff --git a/source/funkin/backend/system/updating/UpdateAvailableScreen.hx b/source/funkin/backend/system/updating/UpdateAvailableScreen.hx index 5b076058..d3a3869a 100644 --- a/source/funkin/backend/system/updating/UpdateAvailableScreen.hx +++ b/source/funkin/backend/system/updating/UpdateAvailableScreen.hx @@ -92,6 +92,8 @@ class UpdateAvailableScreen extends MusicBeatState { add(skipButton); oldPos = FlxG.mouse.getScreenPosition(); + + DiscordUtil.call("onMenuLoaded", ["Update Available Screen"]); } var destY:Float = 0; diff --git a/source/funkin/backend/system/updating/UpdateScreen.hx b/source/funkin/backend/system/updating/UpdateScreen.hx index d57a3d4a..323fda09 100644 --- a/source/funkin/backend/system/updating/UpdateScreen.hx +++ b/source/funkin/backend/system/updating/UpdateScreen.hx @@ -59,8 +59,10 @@ class UpdateScreen extends MusicBeatState { overSound = FlxG.sound.load(Paths.sound('gameOverEnd')); updater.execute(); - + FlxG.camera.addShader(rainbowShader = new CustomShader("engine/updaterShader")); + + DiscordUtil.call("onMenuLoaded", ["Update Screen"]); } @@ -104,7 +106,7 @@ class UpdateScreen extends MusicBeatState { // update is done, play bf's anim FlxG.sound.music.stop(); overSound.play(); - + remove(generalProgress); remove(partProgress); generalProgress = FlxDestroyUtil.destroy(generalProgress); diff --git a/source/funkin/backend/system/updating/UpdateUtil.hx b/source/funkin/backend/system/updating/UpdateUtil.hx index 44b37beb..f8268b93 100644 --- a/source/funkin/backend/system/updating/UpdateUtil.hx +++ b/source/funkin/backend/system/updating/UpdateUtil.hx @@ -11,7 +11,7 @@ using funkin.backend.system.github.GitHub; class UpdateUtil { public static final repoOwner:String = "YoshiCrafter29"; public static final repoName:String = "CodenameTestRepo"; - + public static function init() { // deletes old bak file if it exists #if sys @@ -22,11 +22,11 @@ class UpdateUtil { } public static function checkForUpdates():UpdateCheckCallback { - var curTag = 'v${Application.current.meta.get('version')}'; + var curTag = 'v${Application.current.meta.get('version')}'; trace(curTag); - + var error = false; - + var newUpdates = __doReleaseFiltering(GitHub.getReleases(repoOwner, repoName, function(e) { error = true; }), curTag); @@ -35,7 +35,7 @@ class UpdateUtil { success: false, newUpdate: false }; - + if (newUpdates.length <= 0) { return { success: true, diff --git a/source/funkin/backend/utils/CoolUtil.hx b/source/funkin/backend/utils/CoolUtil.hx index 261e85f7..4817bf5d 100644 --- a/source/funkin/backend/utils/CoolUtil.hx +++ b/source/funkin/backend/utils/CoolUtil.hx @@ -2,6 +2,7 @@ package funkin.backend.utils; import flixel.text.FlxText; import funkin.backend.utils.XMLUtil.TextFormat; +import flixel.util.typeLimit.OneOfTwo; import flixel.tweens.FlxTween; import flixel.system.frontEnds.SoundFrontEnd; import flixel.sound.FlxSound; @@ -9,6 +10,7 @@ import funkin.backend.system.Conductor; import flixel.sound.FlxSoundGroup; import haxe.Json; import haxe.io.Path; +import haxe.io.Bytes; import haxe.xml.Access; import flixel.input.keyboard.FlxKey; import lime.utils.Assets; @@ -77,6 +79,48 @@ class CoolUtil #end } + /** + * Safe saves a file (even adding eventual missing folders) and shows a warning box instead of making the program crash + * @param path Path to save the file at. + * @param content Content of the file to save (as String or Bytes). + */ + @:noUsing public static function safeSaveFile(path:String, content:OneOfTwo, showErrorBox:Bool = true) { + #if sys + try { + addMissingFolders(Path.directory(path)); + if(content is Bytes) sys.io.File.saveBytes(path, content); + else sys.io.File.saveContent(path, content); + } catch(e) { + var errMsg:String = 'Error while trying to save the file: ${Std.string(e).replace('\n', ' ')}'; + Logs.traceColored([Logs.logText(errMsg, RED)], ERROR); + if(showErrorBox) funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); + } + #end + } + + /** + * Creates eventual missing folders to the specified `path` + * + * WARNING: eventual files in `path` will be considered as folders! Just to make possible folders be named as `songs.json` for example + * + * @param path Path to check. + * @return The initial Path. + */ + @:noUsing public static function addMissingFolders(path:String):String { + #if sys + var folders:Array = path.split("/"); + var currentPath:String = ""; + + for (folder in folders) { + currentPath += folder + "/"; + if (!sys.FileSystem.exists(currentPath)) { + sys.FileSystem.createDirectory(currentPath); + } + } + #end + return path; + } + /** * Shortcut to parse a JSON string * @param str Path to the JSON string diff --git a/source/funkin/backend/utils/DiscordUtil.hx b/source/funkin/backend/utils/DiscordUtil.hx index cdd24b6c..9df88b3e 100644 --- a/source/funkin/backend/utils/DiscordUtil.hx +++ b/source/funkin/backend/utils/DiscordUtil.hx @@ -1,62 +1,122 @@ package funkin.backend.utils; import funkin.backend.scripting.events.DiscordPresenceUpdateEvent; +import funkin.backend.scripting.events.CancellableEvent; +import funkin.backend.scripting.*; // lazy +import flixel.util.typeLimit.OneOfTwo; +import openfl.display.BitmapData; +import funkin.backend.system.macros.Utils; import haxe.Json; import flixel.sound.FlxSound; #if DISCORD_RPC -import discord_rpc.DiscordRpc; +import hxdiscord_rpc.Discord; +import hxdiscord_rpc.Types; import sys.thread.Thread; import Sys; import lime.app.Application; #end -class DiscordUtil { - public static var currentID:String = null; +class DiscordUtil +{ + public static var currentID(default, set):String = null; public static var discordThread:#if DISCORD_RPC Thread #else Dynamic #end = null; public static var ready:Bool = false; - public static var data:DiscordJson = null; + public static var initialized:Bool = false; + private static var stopThread:Bool = false; - public static function init() { + public static var user:#if DISCORD_RPC DUser #else Dynamic #end = null; + public static var lastPresence:#if DISCORD_RPC DPresence #else Dynamic #end = null; + public static var config:#if DISCORD_RPC DiscordJson #else Dynamic #end = null; + + public static var script:Script; + + // Constants + public static var REPLY_NO:Int = Discord.REPLY_NO; + public static var REPLY_YES:Int = Discord.REPLY_YES; + public static var REPLY_IGNORE:Int = Discord.REPLY_IGNORE; + public static var PARTY_PRIVATE:Int = Discord.PARTY_PRIVATE; + public static var PARTY_PUBLIC:Int = Discord.PARTY_PUBLIC; + + public static function init() + { #if DISCORD_RPC reloadJsonData(); - discordThread = Thread.create(function() { + if (initialized) + return; + initialized = true; + + discordThread = Thread.create(function() + { while (true) { - while(!ready) { - Sys.sleep(1/60); + while (!stopThread) + { + #if DISCORD_DISABLE_IO_THREAD + Discord.UpdateConnection(); + #end + Discord.RunCallbacks(); + + Sys.sleep(2); } - trace("Processing Discord RPC..."); - DiscordRpc.process(); - Sys.sleep(2); + + Sys.sleep(1); // to reduce cpu } }); - Application.current.onExit.add(function(exitCode) { - shutdown(); - }); + Application.current.onExit.add(function(_) shutdown()); #end } - public static function reloadJsonData() { + public static function reloadJsonData() + { #if DISCORD_RPC - data = {}; var jsonPath = Paths.json("config/discord"); - if (Assets.exists(jsonPath)) { - try { - data = Json.parse(Assets.getText(jsonPath)); - } catch(e) { + if (Assets.exists(jsonPath)) + { + try + config = Json.parse(Assets.getText(jsonPath)) + catch (e) Logs.trace('Couldn\'t load Discord RPC configuration: ${e.toString()}', ERROR); - } } - data.setFieldDefault("clientID", "1027994136193810442"); - data.setFieldDefault("logoKey", "icon"); - data.setFieldDefault("logoText", Application.current.meta.get('title')); + else + { + config = {}; + } - changeClientID(data.clientID); + config.logoKey = config.logoKey.getDefault("icon"); + config.logoText = config.logoText.getDefault(Application.current.meta.get('title')); + config.clientID = config.clientID.getDefault("1027994136193810442"); + currentID = config.clientID; #end } - public static function changePresence(details:String, state:String, ?smallImageKey : String) { + public static function event(name:String, event:T):T + { + if (script != null) + script.call(name, [event]); + return event; + } + + public static function call(name:String, ?args:Array) + { + if (script != null) + script.call(name, args); + } + + public static function loadScript() + { + if (script != null) + { + call("destroy"); + script = FlxDestroyUtil.destroy(script); + } + script = Script.create(Paths.script('data/discord')); + // script.setParent(DiscordUtil); + script.load(); + } + + public static function changePresence(details:String, state:String, ?smallImageKey:String) + { #if DISCORD_RPC changePresenceAdvanced({ state: state, @@ -66,12 +126,14 @@ class DiscordUtil { #end } - public static function changeSongPresence(details:String, state:String, audio:FlxSound, ?smallImageKey : String) { + public static function changeSongPresence(details:String, state:String, audio:FlxSound, ?smallImageKey:String) + { #if DISCORD_RPC var start:Float = 0; var end:Float = 0; - if (audio != null && audio.playing) { + if (audio != null && audio.playing) + { start = Date.now().getTime(); end = start + (audio.length - audio.time); } @@ -86,66 +148,335 @@ class DiscordUtil { #end } - public static function changePresenceAdvanced(data:#if DISCORD_RPC DiscordPresenceOptions #else Dynamic #end) { + public static function changePresenceSince(details:String, state:String, ?smallImageKey:String, ?time:Float) + { #if DISCORD_RPC - if (data == null) return; + if(time == null) + time = Date.now().getTime(); - if (data.largeImageKey == null) - data.largeImageKey = DiscordUtil.data.logoKey; - if (data.largeImageText == null) - data.largeImageText = DiscordUtil.data.logoText; - - #if GLOBAL_SCRIPT - var event = funkin.backend.scripting.GlobalScript.event("onDiscordPresenceUpdate", EventManager.get(DiscordPresenceUpdateEvent).recycle(data)); - if (event.cancelled) return; - #end - - DiscordRpc.presence(data); + changePresenceAdvanced({ + state: state, + details: details, + smallImageKey: smallImageKey, + startTimestamp: Std.int(time / 1000) + }); #end } - public static function changeClientID(id:String) { + + @:noCompletion public static function fixString(str:String) + { + return new cpp.ConstCharStar(cast(str, String)); + } + + @:noCompletion public static function toString(str:cpp.ConstCharStar) + { + return cast(str, String); + } + + public static function changePresenceAdvanced(data:DPresence) + { #if DISCORD_RPC - if (currentID != null) { - DiscordRpc.shutdown(); + if (data == null) + return; + + // copy last presence + if (data.largeImageKey == null) + data.largeImageKey = config.logoKey; + if (data.largeImageText == null) + data.largeImageText = config.logoText; + + var evt = EventManager.get(DiscordPresenceUpdateEvent).recycle(data); + #if GLOBAL_SCRIPT + // kept for "backwards compat" + funkin.backend.scripting.GlobalScript.event("onDiscordPresenceUpdate", evt); + #end + event("onDiscordPresenceUpdate", evt); + if (evt.cancelled) + return; + data = evt.presence; + lastPresence = data; + + var dp:DiscordRichPresence = DiscordRichPresence.create(); + // TODO: make this use a reflection-like macro + Utils.safeSetWrapper(dp.state, data.state, fixString); + Utils.safeSetWrapper(dp.details, data.details, fixString); + Utils.safeSet(dp.startTimestamp, data.startTimestamp); + Utils.safeSet(dp.endTimestamp, data.endTimestamp); + Utils.safeSetWrapper(dp.largeImageKey, data.largeImageKey, fixString); + Utils.safeSetWrapper(dp.largeImageText, data.largeImageText, fixString); + Utils.safeSetWrapper(dp.smallImageKey, data.smallImageKey, fixString); + Utils.safeSetWrapper(dp.smallImageText, data.smallImageText, fixString); + Utils.safeSetWrapper(dp.partyId, data.partyId, fixString); + Utils.safeSet(dp.partySize, data.partySize); + Utils.safeSet(dp.partyMax, data.partyMax); + Utils.safeSet(dp.partyPrivacy, data.partyPrivacy); + Utils.safeSetWrapper(dp.matchSecret, data.matchSecret, fixString); + Utils.safeSetWrapper(dp.joinSecret, data.joinSecret, fixString); + Utils.safeSetWrapper(dp.spectateSecret, data.spectateSecret, fixString); + Utils.safeSet(dp.instance, data.instance); + if (data.matchSecret == null && data.joinSecret == null && data.spectateSecret == null) + { + Utils.safeSetWrapper(dp.button1Label, data.button1Label, fixString); + Utils.safeSetWrapper(dp.button1Url, data.button1Url, fixString); + Utils.safeSetWrapper(dp.button2Label, data.button2Label, fixString); + Utils.safeSetWrapper(dp.button2Url, data.button2Url, fixString); } - ready = false; - - DiscordRpc.start({ - clientID: id, - onReady: function() { - Logs.trace('Discord RPC started'); - ready = true; - }, - onError: onError, - onDisconnected: onDisconnected - }); - currentID = id; - + Discord.UpdatePresence(cpp.RawConstPointer.addressOf(dp)); #end } - public static function shutdown() { + public static function clearPresence() { #if DISCORD_RPC - DiscordRpc.shutdown(); + Discord.ClearPresence(); #end } + private static function set_currentID(id:String):String + { + if (currentID == id) + return id; + #if DISCORD_RPC + if (currentID != null) + shutdown(); + + var handlers:DiscordEventHandlers = DiscordEventHandlers.create(); + handlers.ready = cpp.Function.fromStaticFunction(onReady); + handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected); + handlers.errored = cpp.Function.fromStaticFunction(onError); + handlers.joinGame = cpp.Function.fromStaticFunction(onJoin); + handlers.joinRequest = cpp.Function.fromStaticFunction(onJoinReq); + handlers.spectateGame = cpp.Function.fromStaticFunction(onSpectate); + Discord.Initialize(id, cpp.RawPointer.addressOf(handlers), 1, null); + stopThread = false; + + loadScript(); + #end + + return currentID = id; + } + + public static function shutdown() + { + ready = false; + stopThread = true; + #if DISCORD_RPC + Discord.Shutdown(); + #end + + call("destroy"); + script = FlxDestroyUtil.destroy(script); + } + + public static function respond(userId:String, reply:Int) + { + #if DISCORD_RPC + Discord.Respond(fixString(userId), reply); + #end + } // HANDLERS #if DISCORD_RPC - static function onError(_code:Int, _message:String) { - Logs.trace('Discord RPC Error: ${_message} (Code: $_code)', ERROR); + private static function onReady(request:cpp.RawConstPointer):Void + { + user = DUser.initRaw(request); + + Logs.traceColored([ + Logs.logText("[Discord] ", BLUE), + Logs.logText("Connected to User " + user.globalName + " ("), + Logs.logText(user.handle, GRAY), + Logs.logText(")") + ], INFO); + + ready = true; + + call("onReady", [user]); } - static function onDisconnected(_code:Int, _message:String) { - Logs.trace('Discord RPC Disconnected: ${_message} (Code: $_code)', WARNING); + private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void + { + var finalMsg:String = cast(message, String); + + Logs.traceColored([ + Logs.logText("[Discord] ", BLUE), + Logs.logText("Disconnected ("), + Logs.logText('$errorCode: $finalMsg', RED), + Logs.logText(")") + ], INFO); + + call("onReady", [errorCode, cast(finalMsg, String)]); + } + + private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void + { + var finalMsg:String = cast(message, String); + + Logs.traceColored([ + Logs.logText("[Discord] ", BLUE), + Logs.logText('Error ($errorCode: $finalMsg)', RED) + ], ERROR); + + call("onError", [errorCode, cast(finalMsg, String)]); + } + + private static function onJoin(joinSecret:cpp.ConstCharStar):Void + { + Logs.traceColored([Logs.logText("[Discord] ", BLUE), Logs.logText("Someone has just joined", GREEN)], INFO); + + call("onJoinGame", [cast(joinSecret, String)]); + } + + private static function onSpectate(spectateSecret:cpp.ConstCharStar):Void + { + Logs.traceColored([ + Logs.logText("[Discord] ", BLUE), + Logs.logText("Someone started spectating your game", YELLOW) + ], INFO); + + call("onSpectateGame", [cast(spectateSecret, String)]); + } + + private static function onJoinReq(request:cpp.RawConstPointer):Void + { + Logs.traceColored([ + Logs.logText("[Discord] ", BLUE), + Logs.logText("Someone has just requested to join", YELLOW) + ], WARNING); + + var req:DUser = DUser.initRaw(request); + call("onJoinRequest", [req]); } #end } -typedef DiscordJson = { +typedef DiscordJson = +{ var ?clientID:String; var ?logoKey:String; var ?logoText:String; -} \ No newline at end of file +} + +@:noCustomClass +final class DUser +{ + /** + * The username + discriminator if they have it + **/ + public var handle:String; + + /** + * The user id, aka 860561967383445535 + **/ + public var userId:String; + + /** + * The user's username + **/ + public var username:String; + + /** + * The #number from before discord changed to usernames only, if the user has changed to a username them its just a 0 + **/ + public var discriminator:Int; + + /** + * The user's avatar filename + **/ + public var avatar:String; + + /** + * The user's display name + **/ + public var globalName:String; + + /** + * If the user is a bot or not + **/ + public var bot:Bool; + + /** + * Idk check discord docs + **/ + public var flags:Int; + + /** + * If the user has nitro + **/ + public var premiumType:NitroType; + + private function new() + { + } + + public static function initRaw(req:cpp.RawConstPointer) + { + return init(cpp.ConstPointer.fromRaw(req).ptr); + } + + public static function init(userData:cpp.Star) + { + var d = new DUser(); + d.userId = userData.userId; + d.username = userData.username; + d.discriminator = Std.parseInt(userData.discriminator); + d.avatar = userData.avatar; + d.globalName = userData.globalName; + d.bot = userData.bot; + d.flags = userData.flags; + d.premiumType = userData.premiumType; + + if (d.discriminator != 0) + d.handle = '${d.username}#${d.discriminator}'; + else + d.handle = '${d.username}'; + return d; + } + + /** + * Calling this function gets the BitmapData of the user + **/ + public function getAvatar(size:Int = 256):BitmapData + return BitmapData.fromBytes(HttpUtil.requestBytes('https://cdn.discordapp.com/avatars/$userId/$avatar.png?size=$size')); +} + +enum abstract NitroType(Int) to Int from Int +{ + var NONE = 0; + var NITRO_CLASSIC = 1; + var NITRO = 2; + var NITRO_BASIC = 3; +} + +typedef DPresence = +{ + var ?state:String; /* max 128 bytes */ + var ?details:String; /* max 128 bytes */ + var ?startTimestamp:OneOfTwo; + var ?endTimestamp:OneOfTwo; + var ?largeImageKey:String; /* max 32 bytes */ + var ?largeImageText:String; /* max 128 bytes */ + var ?smallImageKey:String; /* max 32 bytes */ + var ?smallImageText:String; /* max 128 bytes */ + var ?partyId:String; /* max 128 bytes */ + var ?partySize:Int; + var ?partyMax:Int; + var ?partyPrivacy:Int; + var ?matchSecret:String; /* max 128 bytes */ + var ?joinSecret:String; /* max 128 bytes */ + var ?spectateSecret:String; /* max 128 bytes */ + var ?instance:OneOfTwo; + var ?button1Label:String; /* max 32 bytes */ + var ?button1Url:String; /* max 512 bytes */ + var ?button2Label:String; /* max 32 bytes */ + var ?button2Url:String; /* max 512 bytes */ +} + +typedef DEvents = +{ + var ?ready:DUser->Void; + var ?disconnected:(errorCode:Int, message:String) -> Void; + var ?errored:(errorCode:Int, message:String) -> Void; + var ?joinGame:String->Void; + var ?spectateGame:String->Void; + var ?joinRequest:DUser->Void; +} diff --git a/source/funkin/backend/utils/HttpUtil.hx b/source/funkin/backend/utils/HttpUtil.hx new file mode 100644 index 00000000..f2d3caf8 --- /dev/null +++ b/source/funkin/backend/utils/HttpUtil.hx @@ -0,0 +1,71 @@ +package funkin.backend.utils; + +import haxe.Http; + +class HttpUtil +{ + public static var userAgent:String = "request"; + public static function requestText(url:String) + { + var r = null; + var h = new Http(url); + h.setHeader("User-Agent", userAgent); + + h.onStatus = function(s) + { + if (isRedirect(s)) + r = requestText(h.responseHeaders.get("Location")); + }; + + h.onData = function(d) + { + if (r == null) + r = d; + } + h.onError = function(e) + { + throw e; + } + + h.request(false); + return r; + } + + public static function requestBytes(url:String) + { + var r = null; + var h = new Http(url); + h.setHeader("User-Agent", userAgent); + + h.onStatus = function(s) + { + if (isRedirect(s)) + r = requestBytes(h.responseHeaders.get("Location")); + }; + + h.onBytes = function(d) + { + if (r == null) + r = d; + } + h.onError = function(e) + { + throw e; + } + + h.request(false); + return r; + } + + private static function isRedirect(status:Int):Bool + { + switch (status) + { + // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex + case 301 | 302 | 307 | 308: + Logs.traceColored([Logs.logText('[Connection Status] ', BLUE), Logs.logText('Redirected with status code: ', YELLOW), Logs.logText('$status', GREEN)], WARNING); + return true; + } + return false; + } +} diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx index 4c0c40c0..6905d6b2 100644 --- a/source/funkin/backend/utils/MemoryUtil.hx +++ b/source/funkin/backend/utils/MemoryUtil.hx @@ -79,6 +79,8 @@ class MemoryUtil { public static inline function currentMemUsage() { #if cpp return Gc.memInfo64(Gc.MEM_INFO_USAGE); + #elseif hl + return Gc.stats().currentMemory; #elseif sys return cast(cast(System.totalMemory, UInt), Float); #else diff --git a/source/funkin/backend/utils/XMLUtil.hx b/source/funkin/backend/utils/XMLUtil.hx index 0ac1bef7..23ed0516 100644 --- a/source/funkin/backend/utils/XMLUtil.hx +++ b/source/funkin/backend/utils/XMLUtil.hx @@ -180,13 +180,13 @@ class XMLUtil { } /** - * Creates a new sprite based on a XML node. - */ + * Creates a new sprite based on a XML node. + */ public static inline function createSpriteFromXML(node:Access, parentFolder:String = "", defaultAnimType:XMLAnimType = BEAT, ?cl:Class, ?args:Array):FunkinSprite { - if(cl == null) cl = FunkinSprite; - if(args == null) args = []; - return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType); - } + if(cl == null) cl = FunkinSprite; + if(args == null) args = []; + return loadSpriteFromXML(Type.createInstance(cl, args), node, parentFolder, defaultAnimType); + } public static function extractAnimFromXML(anim:Access, animType:XMLAnimType = NONE, loop:Bool = false):AnimData { var animData:AnimData = { diff --git a/source/funkin/editors/DebugOptions.hx b/source/funkin/editors/DebugOptions.hx index 3dc44b22..8dcfeda7 100644 --- a/source/funkin/editors/DebugOptions.hx +++ b/source/funkin/editors/DebugOptions.hx @@ -36,16 +36,36 @@ class DebugOptionsScreen extends OptionsScreen { })); #end add(new Checkbox( - "Enable Editor SFXs", + "Editor SFXs", "If checked, will play sound effects when working on editors (ex: will play sfxs when checking checkboxes...)", "editorSFX")); add(new Checkbox( - "Resizable Editors", - "If checked, editors will be resizable and extensible like other programs instead of zooming in upon maximization.", - "resizableEditors")); + "Pretty Print", + "If checked, the saved files from the editor will be formatted to be easily viewable (does not apply to xmls...)", + "editorPrettyPrint")); add(new Checkbox( "Intensive Blur", "If checked, will use more intensive blur that may be laggier but look better.", "intensiveBlur")); + add(new Checkbox( + "Editor Autosaves", + "If checked, this will autosave your files in the editor, with the settings listed below.", + "charterAutoSaves")); + add(new NumOption( + "Autosaving Time", + "This controls how often the editor will autosave your file (in seconds...)", + 60, 60*10, 30, + "charterAutoSaveTime" + )); + add(new NumOption( + "Save Warning Time", + "This controls how long the editor will warn you before it autosaves (in seconds..., 0 to disable)", + 0, 15, 1, + "charterAutoSaveWarningTime" + )); + add(new Checkbox( + "Autosaves Folder", + "If checked, this will autosave your file in a seperate folder with a time stamp instead of overriding your current file. (song/autosaves/)", + "charterAutoSavesSeperateFolder")); } } \ No newline at end of file diff --git a/source/funkin/editors/SaveSubstate.hx b/source/funkin/editors/SaveSubstate.hx index 6250050a..7202cf37 100644 --- a/source/funkin/editors/SaveSubstate.hx +++ b/source/funkin/editors/SaveSubstate.hx @@ -1,8 +1,5 @@ package funkin.editors; -#if desktop -import sys.io.File; -#end import haxe.io.Path; import lime.ui.FileDialog; @@ -30,13 +27,9 @@ class SaveSubstate extends MusicBeatSubstate { super.create(); var fileDialog = new FileDialog(); - fileDialog.onCancel.add(function() { - close(); - }); + fileDialog.onCancel.add(function() close()); fileDialog.onSelect.add(function(str) { - #if desktop - File.saveContent(str, data); - #end + CoolUtil.safeSaveFile(str, data); close(); }); fileDialog.browse(SAVE, options.saveExt.getDefault(Path.extension(options.defaultSaveFile)), options.defaultSaveFile); @@ -46,11 +39,6 @@ class SaveSubstate extends MusicBeatSubstate { super.update(elapsed); parent.persistentUpdate = false; } - - private function onError(_) { - // TODO: error handling - close(); - } } typedef SaveSubstateData = { diff --git a/source/funkin/editors/SaveWarning.hx b/source/funkin/editors/SaveWarning.hx index 86dedecb..b475e3e2 100644 --- a/source/funkin/editors/SaveWarning.hx +++ b/source/funkin/editors/SaveWarning.hx @@ -21,7 +21,7 @@ class SaveWarning { if (warningFunc != null) warningFunc(closingWindow); if (FlxG.state != null && FlxG.state is UIState) { - FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes!", "Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?", + FlxG.state.openSubState(new UIWarningSubstate("Unsaved Changes!", "Your changes will be lost if you don't save them. (Can't be recovered)\n\n\nWould you like to Cancel?", [ { label: "Cancel", diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index 25c2d33b..359ef24d 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -52,7 +52,7 @@ class CharacterEditor extends UIState { WindowUtils.endfix = " (Character Editor)"; SaveWarning.selectionClass = CharacterSelection; SaveWarning.saveFunc = () -> {_file_save(null);}; - + topMenu = [ { label: "File", @@ -224,7 +224,7 @@ class CharacterEditor extends UIState { FlxG.cameras.add(uiCamera); FlxG.cameras.add(animsCamera); - character = new Character(0,0,__character, false, false); + character = new Character(0,0, __character, false, false); character.debugMode = true; character.cameras = [charCamera]; @@ -257,6 +257,8 @@ class CharacterEditor extends UIState { Framerate.memoryCounter.alpha = 0.4; Framerate.codenameBuildField.alpha = 0.4; } + + DiscordUtil.call("onEditorLoaded", ["Character Editor", __character]); } override function destroy() { @@ -328,14 +330,14 @@ class CharacterEditor extends UIState { function _file_save(_) { #if sys - sys.io.File.saveContent( + CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/data/characters/${character.curCharacter}.xml', buildCharacter() ); undos.save(); - return; - #end + #else _file_saveas(_); + #end } function _file_saveas(_) { diff --git a/source/funkin/editors/character/CharacterInfoScreen.hx b/source/funkin/editors/character/CharacterInfoScreen.hx index d6be7ba7..28425c2e 100644 --- a/source/funkin/editors/character/CharacterInfoScreen.hx +++ b/source/funkin/editors/character/CharacterInfoScreen.hx @@ -181,8 +181,8 @@ class CharacterInfoScreen extends UISubstateWindow { xml.set("antialiasing", antialiasingCheckbox.checked ? "true" : "false"); xml.set("sprite", spriteTextBox.label.text); if (iconColorWheel.colorChanged) - xml.set("color", iconColorWheel.curColor.toHexString(false).replace("0x", "#")); - for (val in customPropertiesButtonList.buttons.members) + xml.set("color", iconColorWheel.curColor.toWebString()); + for (val in customPropertiesButtonList.buttons.members) xml.set(val.propertyText.label.text, val.valueText.label.text); for (anim in character.animDatas) diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index 9d1a60ed..817acf29 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -33,6 +33,8 @@ class CharacterSelection extends EditorTreeMenu })); main = new OptionsScreen("Character Editor", "Select a character to edit", list); + + DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]); } override function createPost() { diff --git a/source/funkin/editors/charter/Charter.hx b/source/funkin/editors/charter/Charter.hx index 38023cfc..1ed3da16 100644 --- a/source/funkin/editors/charter/Charter.hx +++ b/source/funkin/editors/charter/Charter.hx @@ -1,5 +1,11 @@ package funkin.editors.charter; +// ! FUCK YOU CHUF (your biggest fan -lunar) <3 +import flixel.input.FlxPointer; +import openfl.geom.Point; +import openfl.geom.Rectangle; +import flixel.graphics.FlxGraphic; +import flixel.addons.display.FlxTiledSprite; import funkin.editors.ui.UIContextMenu.UIContextMenuOptionSpr; import funkin.editors.ui.UITopMenu.UITopMenuButton; import funkin.editors.charter.CharterStrumline; @@ -16,6 +22,9 @@ import funkin.backend.chart.ChartData; import flixel.addons.display.FlxBackdrop; import funkin.editors.ui.UIContextMenu.UIContextMenuOption; import funkin.editors.ui.UIState; +import flixel.util.FlxColor; +import openfl.display.BitmapData; +import funkin.backend.shaders.CustomShader; class Charter extends UIState { public static var __song:String; @@ -52,6 +61,7 @@ class Charter extends UIState { public var gridBackdropDummy:CharterBackdropDummy; public var noteHoverer:CharterNoteHoverer; + public var noteDeleteAnims:CharterDeleteAnim; public var strumlineInfoBG:FlxSprite; public var strumlineAddButton:CharterStrumlineButton; @@ -76,6 +86,8 @@ class Charter extends UIState { public var charterCamera:FlxCamera; public var uiCamera:FlxCamera; public var selectionBox:UISliceSprite; + public var autoSaveNotif:CharterAutoSaveUI; + public static var autoSaveTimer:Float = 0; public static var selection:Selection; @@ -83,6 +95,7 @@ class Charter extends UIState { public static var undos:UndoList; public static var clipboard:Array = []; + public static var waveformHandler:CharterWaveformHandler; public function new(song:String, diff:String, reload:Bool = true) { super(); @@ -99,7 +112,7 @@ class Charter extends UIState { WindowUtils.endfix = " (Chart Editor)"; SaveWarning.selectionClass = CharterSelection; SaveWarning.saveFunc = () -> {_file_save(null);}; - + topMenu = [ { label: "File", @@ -151,6 +164,15 @@ class Charter extends UIState { onSelect: _file_meta_saveas, }, null, + { + label: "Export For FNF Legacy...", + onSelect: _file_saveas_fnflegacy, + }, + { + label: "Export For Psych Engine...", + onSelect: _file_saveas_psych, + }, + null, { label: "Exit", onSelect: _file_exit @@ -267,6 +289,12 @@ class Charter extends UIState { label: 'Show Beats Separator', onSelect: _view_showeventBeatSeparator, icon: Options.charterShowBeats ? 1 : 0 + }, + null, + { + label: 'Low Detail Waveforms', + onSelect: _view_switchWaveformDetail, + icon: Options.charterLowDetailWaveforms ? 1 : 0 } ] }, @@ -379,8 +407,10 @@ class Charter extends UIState { selectionBox.incorporeal = true; noteHoverer = new CharterNoteHoverer(); + noteDeleteAnims = new CharterDeleteAnim(); - selectionBox.cameras = notesGroup.cameras = gridBackdrops.cameras = noteHoverer.cameras = [charterCamera]; + selectionBox.cameras = notesGroup.cameras = gridBackdrops.cameras = + noteHoverer.cameras = noteDeleteAnims.cameras = [charterCamera]; topMenuSpr = new UITopMenu(topMenu); topMenuSpr.cameras = uiGroup.cameras = [uiCamera]; @@ -420,6 +450,9 @@ class Charter extends UIState { strumlineInfoBG.y = 23; strumlineInfoBG.scrollFactor.set(); + autoSaveNotif = new CharterAutoSaveUI(20, strumlineInfoBG.y + strumlineInfoBG.height + 20); + uiGroup.add(autoSaveNotif); + strumlineAddButton = new CharterStrumlineButton("editors/new", "Create New"); strumlineAddButton.onClick = createStrumWithUI; strumlineAddButton.animationOnClick = false; @@ -448,6 +481,7 @@ class Charter extends UIState { add(addEventSpr); add(eventsGroup); add(noteHoverer); + add(noteDeleteAnims); add(notesGroup); add(selectionBox); add(strumlineInfoBG); @@ -468,6 +502,16 @@ class Charter extends UIState { Framerate.codenameBuildField.alpha = 0.4; } updateDisplaySprites(); + + // ! IF YOU EVER WANNA VIEW IN THE FUTURE, JUST USE A FLXSPRITE :D -lunar + /*var dataDisplay:FlxSprite = new FlxSprite().loadGraphic(waveformHandler.waveDatas.get("Voices.ogg")); + dataDisplay.scrollFactor.set(1, 0); + dataDisplay.scale.set(2, 2); + dataDisplay.updateHitbox(); + dataDisplay.screenCenter(Y); + dataDisplay.cameras = [charterCamera]; dataDisplay.x = -dataDisplay.width; add(dataDisplay);*/ + + DiscordUtil.call("onEditorLoaded", ["Chart Editor", __song + " (" + __diff + ")"]); } override function destroy() { @@ -501,12 +545,12 @@ class Charter extends UIState { for(strL in PlayState.SONG.strumLines) createStrumline(strumLines.members.length, strL, false, false); - + // create notes notesGroup.autoSort = false; var noteCount:Int = 0; for (strL in PlayState.SONG.strumLines) - for (note in strL.notes) noteCount++; + noteCount += strL.notes.length; notesGroup.preallocate(noteCount); var notesCreated:Int = 0; @@ -544,8 +588,10 @@ class Charter extends UIState { buildNoteTypesUI(); refreshBPMSensitive(); - // Undo Stuffs :D - __relinkUndos(); + // Just for now until i add event stacking -lunar + try {__relinkUndos();} + catch (e) {Logs.trace('Failed to relink undos: ${Std.string(e)}', ERROR);} + __applyPlaytestInfo(); } @@ -555,8 +601,55 @@ class Charter extends UIState { var length = FlxG.sound.music.getDefault(vocals).length; scrollBar.length = __endStep = Conductor.getStepForTime(length); - gridBackdrops.bottomLimitY = Conductor.getStepForTime(length) * 40; + gridBackdrops.bottomLimitY = __endStep * 40; eventsBackdrop.bottomSeparator.y = gridBackdrops.bottomLimitY-2; + + updateWaveforms(); + } + + public function updateWaveforms() { + var wavesToGenerate:Array<{name:String, sound:FlxSound}> = [ + {name: "Inst.ogg", sound: FlxG.sound.music}, + ]; + if (PlayState.SONG.meta.needsVoices != false) + wavesToGenerate.push({name: "Voices.ogg", sound: vocals}); + + for (strumLine in strumLines) + if (strumLine.vocals != null && strumLine.strumLine.vocalsSuffix != null && strumLine.strumLine.vocalsSuffix != "") + wavesToGenerate.push({ + name: 'Voices${strumLine.strumLine.vocalsSuffix}.ogg', + sound: strumLine.vocals + }); + + var oldWaveformList:Array = waveformHandler.waveformList; + var newWaveformList:Array = [for (data in wavesToGenerate) data.name]; + + if (waveformHandler == null ? true : waveformHandler.ampsNeeded != __endStep*40) { + waveformHandler.clearWaveforms(); + waveformHandler.ampsNeeded = __endStep * 40; + + for (data in wavesToGenerate) { + waveformHandler.generateShader(data.name, data.sound); + waveformHandler.waveformList.push(data.name); + } + } else if (waveformHandler != null && waveformHandler.waveformList.length != newWaveformList.length) { + for (name in oldWaveformList) + if (!newWaveformList.contains(name)) + waveformHandler.clearWaveform(name); + + for (data in wavesToGenerate) + if (!oldWaveformList.contains(data.name)) + waveformHandler.generateShader(data.name, data.sound); + + waveformHandler.waveformList = newWaveformList; + } + + for (strumLine in strumLines) { + if (strumLine.selectedWaveform == -1) continue; + + var oldName:String = oldWaveformList[strumLine.selectedWaveform]; + strumLine.selectedWaveform = waveformHandler.waveformList.indexOf(oldName); + } } public override function beatHit(curBeat:Int) { @@ -598,6 +691,36 @@ class Charter extends UIState { for(s in selection) s.selected = true; } + var __autoSaveLocation:String = null; + public function updateAutoSaving(elapsed:Float) { + if (!Options.charterAutoSaves) return; + autoSaveTimer -= elapsed; + + if (autoSaveTimer < Options.charterAutoSaveWarningTime && !autoSaveNotif.cancelled && !autoSaveNotif.showedAnimation) { + if (Options.charterAutoSavesSeperateFolder) + __autoSaveLocation = __diff.toLowerCase() + DateTools.format(Date.now(), "%m-%d_%H-%M"); + autoSaveNotif.startAutoSave(autoSaveTimer, + !Options.charterAutoSavesSeperateFolder ? 'Saved chart at ${__diff.toLowerCase()}.json!' : + 'Saved chart at $__autoSaveLocation.json!' + ); + } + if (autoSaveTimer <= 0) { + autoSaveTimer = Options.charterAutoSaveTime; + if (!autoSaveNotif.cancelled) { + buildChart(); + var songPath:String = '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}'; + + if (Options.charterAutoSavesSeperateFolder) + Chart.save(songPath, PlayState.SONG, __autoSaveLocation, {saveMetaInChart: false, folder: "autosaves", prettyPrint: Options.editorPrettyPrint}); + else + Chart.save(songPath, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, prettyPrint: Options.editorPrettyPrint}); + undos.save(); + } + autoSaveNotif.cancelled = false; + } + } + + var deletedNotes:Selection = new Selection(); public function updateNoteLogic(elapsed:Float) { updateSelectionLogic(); @@ -669,11 +792,11 @@ class Charter extends UIState { for (s in selection) { if (s.draggable) { var changePoint:FlxPoint = FlxPoint.get(verticalChange, horizontalChange); - if (!FlxG.keys.pressed.SHIFT) + if (!FlxG.keys.pressed.SHIFT) changePoint.x -= ((s.step + verticalChange) - quantStepRounded(s.step+verticalChange, verticalChange > 0 ? 0.35 : 0.65)); var boundedChange:FlxPoint = changePoint.clone(); - + // Some maths, so cool bro -lunar (i dont know why i quopte my self here) if (s.step + changePoint.x < 0) boundedChange.x += Math.abs(s.step + changePoint.x); if (s.step + changePoint.x > __endStep-1) boundedChange.x -= (s.step + changePoint.x) - (__endStep-1); @@ -702,7 +825,11 @@ class Charter extends UIState { } case NONE: if (FlxG.mouse.justPressed) - FlxG.mouse.getWorldPosition(charterCamera, dragStartPos); + FlxG.mouse.getWorldPosition(charterCamera, dragStartPos); + else if (FlxG.mouse.justPressedRight) { + closeCurrentContextMenu(); + gridActionType = DELETE_SELECTION; + } if (gridBackdropDummy.hovered) { // AUTO DETECT @@ -711,11 +838,11 @@ class Charter extends UIState { var id = Math.floor(mousePos.x / 40); var mouseOnGrid = id >= 0 && id < 4 * gridBackdrops.strumlinesAmount && mousePos.y >= 0; - + if (FlxG.mouse.justReleased) { for (n in selection) n.selected = false; selection = []; - + if (mouseOnGrid && mousePos.y > 0 && mousePos.y < (__endStep)*40) { var note = new CharterNote(); note.updatePos( @@ -745,13 +872,6 @@ class Charter extends UIState { } } } - - if (FlxG.mouse.justReleasedRight) { - var mousePos = FlxG.mouse.getScreenPosition(uiCamera); - closeCurrentContextMenu(); - openContextMenu(topMenu[1].childs, null, mousePos.x, mousePos.y); - mousePos.put(); - } case SUSTAIN_DRAG: selectionDragging = FlxG.mouse.pressed; if (selectionDragging) { @@ -760,7 +880,7 @@ class Charter extends UIState { var change:Float = Math.max((mousePos.y-(FlxG.keys.pressed.SHIFT ? dragStartPos.y : quantStep(dragStartPos.y))) / 40, -n.susLength); n.tempSusLength = change; - if (!FlxG.keys.pressed.SHIFT) + if (!FlxG.keys.pressed.SHIFT) n.tempSusLength -= (n.susLength + change) - quantStepRounded(n.susLength + change, change > 0 ? 0.35 : 0.65); @:privateAccess n.__susInstaLerp = FlxG.keys.pressed.SHIFT; }); @@ -777,15 +897,38 @@ class Charter extends UIState { undoChanges.push({before: oldSusLen, after: n.susLength, note: n}); }); undos.addToUndo(CEditSustains(undoChanges)); - + gridActionType = NONE; currentCursor = ARROW; } + case DELETE_SELECTION: + notesGroup.forEach(function(n) { + if (n.hovered || n.sustainDraggable) { + deletedNotes.push(n); + deleteSingleSelection(n, false); + + if (selection.contains(n)) selection.remove(n); + noteDeleteAnims.deleteNotes.push({ + note: n, time: noteDeleteAnims.deleteTime + }); + } + }); + + if (FlxG.mouse.justReleasedRight) { + if (deletedNotes.length > 0) undos.addToUndo(CDeleteSelection(deletedNotes.copy())); + else if (noteDeleteAnims.garbageIcon.alpha <= .5) { + var mousePos = FlxG.mouse.getScreenPosition(uiCamera); + closeCurrentContextMenu(); + openContextMenu(topMenu[1].childs, null, mousePos.x, mousePos.y); + mousePos.put(); + } + gridActionType = NONE; deletedNotes = []; + } } addEventSpr.selectable = !selectionBox.visible; var inBoundsY:Bool = (mousePos.y > 0 && mousePos.y < (__endStep)*40); - + // Event Spr if (mousePos.x < 0 && mousePos.x > -addEventSpr.bWidth && gridActionType == NONE && inBoundsY) { addEventSpr.incorporeal = false; @@ -927,7 +1070,7 @@ class Charter extends UIState { var i = 0; var toBeDeleted:Selection = []; for (note in notesGroup.members) - if (note.strumLineID == strumLineID) { + if (note.strumLineID == strumLineID) { undoNotes.push(buildNote(note)); toBeDeleted.push(note); } @@ -977,6 +1120,8 @@ class Charter extends UIState { strumLines.members[strID].updateInfo(); undos.addToUndo(CEditStrumLine(strID, oldData, _)); + + if (oldData.vocalsSuffix != _.vocalsSuffix) updateWaveforms(); })); } @@ -997,6 +1142,7 @@ class Charter extends UIState { var __firstFrame:Bool = true; public override function update(elapsed:Float) { updateNoteLogic(elapsed); + updateAutoSaving(elapsed); if (FlxG.sound.music.playing || __firstFrame) { gridBackdrops.conductorSprY = curStepFloat * 40; @@ -1019,8 +1165,8 @@ class Charter extends UIState { var buttonI:Int = 0; for (button in quantButtons) { - button.visible = ((button.quant == quant) || - (button.quant == quants[FlxMath.wrap(quants.indexOf(quant)-1, 0, quants.length-1)]) || + button.visible = ((button.quant == quant) || + (button.quant == quants[FlxMath.wrap(quants.indexOf(quant)-1, 0, quants.length-1)]) || (button.quant == quants[FlxMath.wrap(quants.indexOf(quant)+1, 0, quants.length-1)])); button.selectable = button.visible; if (!button.visible) continue; @@ -1039,7 +1185,7 @@ class Charter extends UIState { noteTypeText.y = Std.int((noteTopButton.bHeight - noteTypeText.height) / 2); } noteTypeText.text = '($noteType) ${noteTypes[noteType-1] == null ? "Default Note" : noteTypes[noteType-1]}'; - + super.update(elapsed); scrollBar.size = (FlxG.height / 40 / charterCamera.zoom); @@ -1141,7 +1287,7 @@ class Charter extends UIState { } function _file_saveas(_) { - openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false)), { + openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false), null, Options.editorPrettyPrint ? "\t" : null), { defaultSaveFile: '${__diff.toLowerCase()}.json' })); undos.save(); @@ -1157,7 +1303,7 @@ class Charter extends UIState { } function _file_saveas_no_events(_) { - openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false)), { + openSubState(new SaveSubstate(Json.stringify(Chart.filterChartForSaving(PlayState.SONG, false, false), null, Options.editorPrettyPrint ? "\t" : null), { defaultSaveFile: '${__diff.toLowerCase()}.json' })); undos.save(); @@ -1165,13 +1311,13 @@ class Charter extends UIState { function _file_meta_save(_) { #if sys - sys.io.File.saveContent( + CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/meta.json', Json.stringify(PlayState.SONG.meta == null ? {} : PlayState.SONG.meta, null, "\t") ); - return; - #end + #else _file_meta_saveas(_); + #end } function _file_meta_saveas(_) { @@ -1180,20 +1326,32 @@ class Charter extends UIState { })); } + function _file_saveas_fnflegacy(_) { + openSubState(new SaveSubstate(Json.stringify(FNFLegacyParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), { + defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json', + })); + } + + function _file_saveas_psych(_) { + openSubState(new SaveSubstate(Json.stringify(PsychParser.encode(PlayState.SONG), null, Options.editorPrettyPrint ? "\t" : null), { + defaultSaveFile: '${__song.toLowerCase().replace(" ", "-")}${__diff.toLowerCase() == "normal" ? "" : '-${__diff.toLowerCase()}'}.json', + })); + } + function _file_events_save(_) { #if sys - sys.io.File.saveContent( + CoolUtil.safeSaveFile( '${Paths.getAssetsRoot()}/songs/${__song.toLowerCase()}/events.json', - Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}) + Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? "\t" : null) ); - return; - #end + #else _file_events_saveas(_); + #end } function _file_events_saveas(_) { #if sys - openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}), { + openSubState(new SaveSubstate(Json.stringify({events: PlayState.SONG.events == null ? [] : PlayState.SONG.events}, null, Options.editorPrettyPrint ? "\t" : null), { defaultSaveFile: 'events.json' })); #end @@ -1202,7 +1360,7 @@ class Charter extends UIState { #if sys function saveTo(path:String, separateEvents:Bool = false) { buildChart(); - Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: !separateEvents}); + Chart.save(path, PlayState.SONG, __diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: !separateEvents, prettyPrint: Options.editorPrettyPrint}); } #end @@ -1245,7 +1403,7 @@ class Charter extends UIState { } selection = sObjects; _edit_copy(_); // to fix stupid bugs - + undos.addToUndo(CCreateSelection(sObjects.copy())); } @@ -1258,12 +1416,15 @@ class Charter extends UIState { function _edit_delete(_) { if (selection == null || selection.length == 0) return; - selection = deleteSelection(selection); + selection.loop((n:CharterNote) -> { + noteDeleteAnims.deleteNotes.push({note: n, time: noteDeleteAnims.deleteTime}); + }); + selection = deleteSelection(selection, true); } function _edit_undo(_) { - if (strumLines.isDragging || selectionDragging || subState != null) return; - + if (strumLines.isDragging || selectionDragging || (subState != null && !(subState is UIContextMenu))) return; + selection = []; var undo = undos.undo(); switch(undo) { @@ -1286,7 +1447,7 @@ class Charter extends UIState { case CSelectionDrag(selectionDrags): for (s in selectionDrags) if (s.selectable.draggable) s.selectable.handleDrag(s.change * -1); - + selection = [for (s in selectionDrags) s.selectable]; case CEditSustains(changes): for(n in changes) @@ -1308,7 +1469,7 @@ class Charter extends UIState { } function _edit_redo(_) { - if (strumLines.isDragging || selectionDragging || subState != null) return; + if (strumLines.isDragging || selectionDragging || (subState != null && !(subState is UIContextMenu))) return; selection = []; var redo = undos.redo(); @@ -1367,7 +1528,7 @@ class Charter extends UIState { function chart_edit_data(_) FlxG.state.openSubState(new ChartDataScreen(PlayState.SONG)); function chart_edit_metadata(_) - FlxG.state.openSubState(new MetaDataScreen(PlayState.SONG.meta)); + FlxG.state.openSubState(new CharterMetaDataScreen(PlayState.SONG.meta)); function _playback_play(_) { if (Conductor.songPosition >= FlxG.sound.music.getDefault(vocals).length - Conductor.songOffset) return; @@ -1439,6 +1600,11 @@ class Charter extends UIState { t.icon = (Options.charterShowBeats = !Options.charterShowBeats) ? 1 : 0; eventsBackdrop.eventBeatSeparator.visible = gridBackdrops.beatsVisible = Options.charterShowBeats; } + function _view_switchWaveformDetail(t) { + t.icon = (Options.charterLowDetailWaveforms = !Options.charterLowDetailWaveforms) ? 1 : 0; + for (shader in waveformHandler.waveShaders) shader.data.lowDetail.value = [Options.charterLowDetailWaveforms]; + } + inline function _snap_increasesnap(_) changequant(1); inline function _snap_decreasesnap(_) changequant(-1); inline function _snap_resetsnap(_) setquant(16); @@ -1466,7 +1632,7 @@ class Charter extends UIState { null ]; - for (_quant in quants) + for (_quant in quants) newChilds.push({ label: '${_quant}x Grid Snap', onSelect: (_) -> {setquant(_quant); buildSnapsUI();}, @@ -1485,7 +1651,7 @@ class Charter extends UIState { function _note_selectall(_) { selection = [for (note in notesGroup.members) note]; - } + } function _note_selectmeasure(_) { selection = [for (note in notesGroup.members) @@ -1614,7 +1780,7 @@ class Charter extends UIState { } notesGroup.sortNotes(); for(n in notesGroup.members) { - if (PlayState.SONG.strumLines[n.strumLineID] != null) + if (PlayState.SONG.strumLines[n.strumLineID] != null) PlayState.SONG.strumLines[n.strumLineID].notes.push(buildNote(n)); } buildEvents(); @@ -1681,7 +1847,7 @@ class Charter extends UIState { newChanges[i] = CSelectionDrag([ for (selectionDrag in selectionDrags) { - selectable: __relinkSingleSelection(selectionDrag.selectable), + selectable: __relinkSingleSelection(selectionDrag.selectable), change: selectionDrag.change } ]); @@ -1714,10 +1880,13 @@ class Charter extends UIState { selection = new Selection(); undos = new UndoList(); clipboard = []; playtestInfo = null; + waveformHandler = new CharterWaveformHandler(); + autoSaveTimer = Options.charterAutoSaveTime; } @:noCompletion public function __clearStatics() { - selection = null; undos = null; clipboard = null; playtestInfo = null; + selection = null; undos = null; clipboard = null; playtestInfo = null; + waveformHandler.destroy(); Charter.waveformHandler = null; autoSaveTimer = 0; } @:noCompletion public function __updatePlaytestInfo() { @@ -1725,10 +1894,11 @@ class Charter extends UIState { songPosition: Conductor.songPosition, playbackSpeed: playBackSlider.value, quantSelected: quant, - noteTypeSelected: noteType, + noteTypeSelected: noteType, strumlinesDraggable: strumLines.draggable, hitSounds: [for (strumLine in strumLines.members) strumLine.hitsounds], mutedVocals: [for (strumLine in strumLines.members) !(strumLine.vocals.volume > 0)], + waveforms: [for (strumLine in strumLines.members) strumLine.selectedWaveform] } } @@ -1745,6 +1915,8 @@ class Charter extends UIState { strumLine.hitsounds = playtestInfo.hitSounds[i]; for (i => strumLine in strumLines.members) strumLine.vocals.volume = playtestInfo.mutedVocals[i] ? 0 : 1; + for (i => strumLine in strumLines.members) + strumLine.selectedWaveform = playtestInfo.waveforms[i]; } } @@ -1814,6 +1986,7 @@ enum abstract CharterGridActionType(Int) { var NOTE_DRAG = 2; var INVALID_DRAG = 3; var SUSTAIN_DRAG = 4; + var DELETE_SELECTION = 5; } typedef PlaytestInfo = { @@ -1824,4 +1997,5 @@ typedef PlaytestInfo = { var strumlinesDraggable:Bool; var hitSounds:Array; var mutedVocals:Array; + var waveforms:Array; } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterAutoSaveUI.hx b/source/funkin/editors/charter/CharterAutoSaveUI.hx new file mode 100644 index 00000000..d412bc16 --- /dev/null +++ b/source/funkin/editors/charter/CharterAutoSaveUI.hx @@ -0,0 +1,129 @@ +package funkin.editors.charter; + +import flixel.util.FlxTimer; +import flixel.tweens.FlxTween; +import flixel.math.FlxRect; + +using flixel.util.FlxSpriteUtil; + +class CharterAutoSaveUI extends UISliceSprite { + public var icon:FlxSprite; + public var autosavingText:UIText; + public var progressBarBack:FlxSprite; + public var progressBar:FlxSprite; + + public var cancelButton:UIButton; + public var cancelIcon:FlxSprite; + + public var showedAnimation:Bool = false; + public var cancelled:Bool = false; + + public function new(x:Float, y:Float) { + super(x, y, 300, 46, "editors/ui/inputbox"); + + icon = new FlxSprite(x+12, y+9).loadGraphic(Paths.image("editors/autosave-icons"), true, 10, 10); + icon.animation.add("icon", [for(i in 0...3) i], 0, true); + icon.animation.play("icon"); icon.animation.curAnim.curFrame = 0; + members.push(icon); + + members.push(autosavingText = new UIText(x+12+10+4, y+8, 0, "Autosaving in 10 seconds...", 12)); + + progressBarBack = new FlxSprite(x + 10, y + bHeight - 20).makeGraphic(Std.int(bWidth-20), 10, 0x00000000, true); + progressBarBack.drawRoundRect(0, 0, progressBarBack.width, progressBarBack.height, 4, 6, 0xFF727272, null, {smoothing: false}); + progressBarBack.drawRoundRect(1, 1, progressBarBack.width-2, progressBarBack.height-2, 4, 6, 0xFF0D0D0D, null, {smoothing: false}); + members.push(progressBarBack); progressBarBack.antialiasing = false; + + progressBar = new FlxSprite(x + 10, y + bHeight - 20).makeGraphic(Std.int(bWidth-20), 10, 0x00000000, true); + progressBar.drawRoundRect(0, 0, progressBar.width, progressBar.height, 4, 6, 0xFF727272, null, {smoothing: false}); + progressBar.drawRoundRect(1, 1, progressBar.width-2, progressBar.height-2, 4, 6, 0xFFFFFFFF, null, {smoothing: false}); + progressBar.antialiasing = false; progressBar.clipRect = new FlxRect(0, 0, progressBar.width, progressBar.height); + members.push(progressBar); + + cancelButton = new UIButton(x-(10+16), y+8, "", () -> { + if (cancelled || !showedAnimation) return; + cancelled = true; __timer.cancel(); __tween.cancel(); + + icon.animation.curAnim.curFrame = 2; + (new FlxTimer()).start(1, (_) -> {disappearAnimation(true);}); + autosavingText.text += " (Canceled)!"; cancelButton.visible = false; + for (member in [this, autosavingText]) member.color = 0xFFE67F7F; + for (member in [progressBar, progressBarBack]) member.color = 0xFFE67F7F; + }, 24, 14); + cancelButton.frames = Paths.getFrames("editors/ui/grayscale-button"); + cancelButton.color = 0xFFAC3D3D; cancelButton.alpha = 0.2; + members.push(cancelButton); cancelButton.visible = false; + + cancelIcon = new FlxSprite(x-(10+16), y+8).loadGraphic(Paths.image("editors/autosave-delete")); + cancelIcon.color = 0xFFD60E0E; + members.push(cancelIcon); + + alpha = 0; + } + + var __timer:FlxTimer; + var __tween:FlxTween; + + public function startAutoSave(time:Float, sucessText:String) { + appearAnimation(); + + __tween = FlxTween.num(0, 1, time, null, (v:Float) -> { + if ((progress = v) < .95) { + autosavingText.text = 'Autosaving in ${Math.min(Math.round(time), 1+Math.floor(Math.abs(time-(progress*time))))} seconds'; + autosavingText.text += [for (i in 0...(Math.floor((progress*time*3)%4))) "."].join(""); + } + }); + __timer = new FlxTimer(); + __timer.start(time, (_) -> { + autosavingText.text = sucessText; cancelButton.visible = false; + + icon.animation.curAnim.curFrame = 1; + for (member in [this, progressBar, progressBarBack, autosavingText]) member.color = 0xFFA3EC95; + + (new FlxTimer()).start(1, (_) -> {disappearAnimation();}); + }); + } + + public var progress:Float = 0; + public override function update(elapsed:Float) { + super.update(elapsed); + + cancelButton.follow(this, bWidth-(10+24), 7); + cancelButton.alpha = alpha * .7; + + cancelIcon.follow(cancelButton, 3.5+1, 2); + cancelIcon.alpha = alpha; + + cancelIcon.visible = cancelButton.visible; + + icon.follow(this, 12, 9); + icon.alpha = alpha; + + autosavingText.follow(this, 12+10+4, 8); + autosavingText.alpha = alpha; + + for (bar in [progressBar, progressBarBack]) { + bar.follow(this, 10, bHeight-20); + bar.alpha = alpha; + } + progressBar.clipRect = progressBar.clipRect.set(0, 0, progressBar.width*progress,progressBar.height); + } + + public function appearAnimation() { + autosavingText.text = "Autosaving in seconds..."; progress = 0; icon.animation.curAnim.curFrame = 0; + for (member in [this, autosavingText, progressBar, progressBarBack]) member.color = 0xFFFFFFFF; + + x = -(320); alpha=0; FlxTween.cancelTweensOf(this); cancelled = false; cancelButton.visible = true; + FlxTween.tween(this, {x: 20}, .4, {ease: FlxEase.circInOut}); + FlxTween.tween(this, {alpha: 1}, .3, {ease: FlxEase.sineOut, startDelay: .1}); + + showedAnimation = true; + } + + public function disappearAnimation(?canned:Bool = false) { + x = 20; alpha = 1; FlxTween.cancelTweensOf(this); + if (canned) FlxTween.tween(this, {x: -320}, .4, {ease: FlxEase.circInOut}); + FlxTween.tween(this, {alpha: 0}, .3, {ease: FlxEase.sineOut}); + + showedAnimation = false; + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterBackdropGroup.hx b/source/funkin/editors/charter/CharterBackdropGroup.hx index f99571ab..e9240ad1 100644 --- a/source/funkin/editors/charter/CharterBackdropGroup.hx +++ b/source/funkin/editors/charter/CharterBackdropGroup.hx @@ -46,7 +46,7 @@ class CharterBackdropGroup extends FlxTypedGroup { public override function update(elapsed:Float) { for (grid in members) grid.active = grid.visible = false; - + super.update(elapsed); for (i => strumLine in strumLineGroup.members) { @@ -64,6 +64,8 @@ class CharterBackdropGroup extends FlxTypedGroup { grid.sectionSeparator.visible = sectionsVisible; grid.beatSeparator.visible = beatsVisible; + grid.waveformSprite.shader = strumLine.waveformShader; + grid.notesGroup.clear(); notesGroup.forEach((n) -> { var onStr:Bool = (n.snappedToStrumline ? n.strumLineID : Std.int(FlxMath.bound((n.x+n.width)/(40*4), 0, strumLineGroup.members.length-1))) == i; @@ -74,7 +76,6 @@ class CharterBackdropGroup extends FlxTypedGroup { grid.active = grid.visible = true; grid.updateSprites(); } - } public var draggingObj:CharterBackdrop = null; @@ -115,6 +116,8 @@ class CharterBackdrop extends FlxTypedGroup { public var bottomLimit:FlxSprite; public var bottomSeparator:FlxSprite; + public var waveformSprite:FlxSprite; + public var conductorFollowerSpr:FlxSprite; public var beatSeparator:FlxBackdrop; public var sectionSeparator:FlxBackdrop; @@ -128,6 +131,11 @@ class CharterBackdrop extends FlxTypedGroup { gridBackDrop = new FlxBackdrop(gridGraphic, Y, 0, 0); add(gridBackDrop); + waveformSprite = new FlxSprite().makeSolid(1, 1, 0xFF000000); + waveformSprite.scale.set(160, 1); + waveformSprite.updateHitbox(); + add(waveformSprite); + sectionSeparator = new FlxBackdrop(null, Y, 0, 0); sectionSeparator.y = -2; sectionSeparator.visible = Options.charterShowSections; @@ -135,7 +143,7 @@ class CharterBackdrop extends FlxTypedGroup { beatSeparator = new FlxBackdrop(null, Y, 0, 0); beatSeparator.y = -1; beatSeparator.visible = Options.charterShowBeats; - + for(sep in [sectionSeparator, beatSeparator]) { sep.makeSolid(1, 1, -1); sep.alpha = 0.5; @@ -192,8 +200,9 @@ class CharterBackdrop extends FlxTypedGroup { alpha = strumLine.strumLine.visible ? 0.9 : 0.4; } else alpha = 0.9; - for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit, topSeparator, bottomSeparator, conductorFollowerSpr]) { - spr.x = x; spr.alpha = alpha; + for (spr in [gridBackDrop, sectionSeparator, beatSeparator, topLimit, bottomLimit, + topSeparator, bottomSeparator, conductorFollowerSpr, waveformSprite]) { + spr.x = x; if (spr != waveformSprite) spr.alpha = alpha; spr.cameras = this.cameras; } @@ -206,6 +215,25 @@ class CharterBackdrop extends FlxTypedGroup { bottomLimit.scale.set(4 * 40, Math.ceil(FlxG.height / cameras[0].zoom)); bottomLimit.updateHitbox(); + + waveformSprite.visible = waveformSprite.shader != null; + if (waveformSprite.shader == null) return; + + waveformSprite.scale.set(160, FlxG.height * (1/cameras[0].zoom)); + waveformSprite.updateHitbox(); + + waveformSprite.y = (cameras[0].scroll.y+FlxG.height/2)-(waveformSprite.height/2); + + if (waveformSprite.y < 0) {waveformSprite.scale.y += waveformSprite.y; waveformSprite.y = 0;} + if (waveformSprite.y + waveformSprite.height > bottomLimit.y) { + waveformSprite.scale.y -= (waveformSprite.y + waveformSprite.height)-(bottomLimit.y); + waveformSprite.y = (bottomLimit.y) - waveformSprite.scale.y; + } + waveformSprite.updateHitbox(); + + waveformSprite.shader.data.pixelOffset.value = [Math.max(conductorFollowerSpr.y - ((FlxG.height * (1/cameras[0].zoom)) * 0.5), 0)]; + waveformSprite.shader.data.textureRes.value = [waveformSprite.width, waveformSprite.height]; + waveformSprite.shader.data.playerPosition.value = [conductorFollowerSpr.y]; } } diff --git a/source/funkin/editors/charter/CharterDeleteAnim.hx b/source/funkin/editors/charter/CharterDeleteAnim.hx new file mode 100644 index 00000000..df3f565b --- /dev/null +++ b/source/funkin/editors/charter/CharterDeleteAnim.hx @@ -0,0 +1,86 @@ +package funkin.editors.charter; + +class CharterDeleteAnim extends CharterNote { + public var garbageIcon:FlxSprite; + public var garbageCircle:FlxSprite; + + public var deleteNotes:Array<{note:CharterNote, time:Float}> = []; + public var deleteTime:Float = .4; + + public function new() { + super(); + + sustainSpr.color = 0xFF630000; color = 0xFF605A5A; + snappedToStrumline = selectable = autoAlpha = false; + @:privateAccess __animSpeed = 1.25; + + garbageIcon = new FlxSprite().loadGraphic(Paths.image("editors/deleter")); + garbageIcon.color = 0xFF880000; + garbageIcon.cameras = [Charter.instance.uiCamera]; + + garbageCircle = new FlxSprite().loadGraphic(Paths.image("editors/deleter circle")); + garbageCircle.color = 0xFF880000; garbageCircle.alpha = .6; + garbageCircle.cameras = [Charter.instance.uiCamera]; + } + + var __garbageAlpha:Float = 0; + var __deletionTimer:Float = .1; + @:noCompletion var __mousePos:FlxPoint = FlxPoint.get(); + public override function update(elapsed:Float) { + for (deleteData in deleteNotes) { + deleteData.time -= elapsed; + if (deleteData.time < 0) deleteNotes.remove(deleteData); + } + + if (FlxG.mouse.justPressedRight) __deletionTimer = .1; + __deletionTimer -= elapsed; + + __garbageAlpha = FlxMath.lerp(__garbageAlpha, FlxG.mouse.pressedRight && __deletionTimer <= 0 ? 1 : 0, 1/10); + + FlxG.mouse.getWorldPosition(Charter.instance.uiCamera, __mousePos); + if (FlxG.mouse.pressedRight && __deletionTimer <= 0) { + garbageIcon.setPosition( + __mousePos.x + garbageIcon.width/2 + (.65*FlxG.random.float(-1, 1)) + 2, + __mousePos.y - garbageIcon.height + (.65*FlxG.random.float(-1, 1)) - 8 + ); + + garbageCircle.setPosition( + __mousePos.x - garbageCircle.width/2, + (__mousePos.y - garbageCircle.height/2) + 2 + ); + } + + garbageIcon.alpha = __garbageAlpha; + garbageCircle.alpha = __garbageAlpha*.6; + } + + public override function draw() @:privateAccess { + for (deleteData in deleteNotes) { + y = deleteData.note.y + (deleteData.time>deleteTime*.5 ? (deleteData.time/deleteTime)*FlxG.random.float(-1.1, 1.1) : 0); // lunar when no shake :(( + x = deleteData.note.x + (deleteData.time>deleteTime*.5 ? (deleteData.time/deleteTime)*FlxG.random.float(-1.1, 1.1) : 0); // lunar when no shake :(( + angle = deleteData.note.angle; alpha = 1; + animation.curAnim.curFrame = 3; + + sustainSpr.scale.set(10, (40 * deleteData.note.susLength) + (height/2)); + sustainSpr.updateHitbox(); sustainSpr.follow(this, 15, 20); + sustainSpr.exists = deleteData.note.susLength != 0; sustainSpr.alpha = .8; + + typeText.text = Std.string(deleteData.note.type); + typeText.exists = deleteData.note.type != 0; typeText.alpha = .4; + typeText.follow(this, 20 - (typeText.frameWidth/2), 20 - (typeText.frameHeight/2)); + + for (member in [this, sustainSpr, typeText]) + member.alpha *= FlxEase.quadInOut(deleteData.time/deleteTime); + + super.draw(); + } + + if (garbageIcon.alpha > 0) garbageIcon.draw(); + if (garbageCircle.alpha > 0) garbageCircle.draw(); + } + + public override function destroy() { + super.destroy(); + __mousePos.put(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterEvent.hx b/source/funkin/editors/charter/CharterEvent.hx index ff62f465..89a5d0d9 100644 --- a/source/funkin/editors/charter/CharterEvent.hx +++ b/source/funkin/editors/charter/CharterEvent.hx @@ -51,7 +51,7 @@ class CharterEvent extends UISliceSprite implements ICharterSelectable { if (Assets.exists(Paths.pack('events/$name'))) { var packimg = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; if (isBase64 = (packimg != null)) - path = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; + path = Assets.getText(Paths.pack('events/$name')).split('________PACKSEP________')[3]; } var spr = new FlxSprite().loadGraphic(isBase64 ? openfl.display.BitmapData.fromBase64(path.trim(), 'UTF8') : path); return spr; diff --git a/source/funkin/editors/charter/CharterEventScreen.hx b/source/funkin/editors/charter/CharterEventScreen.hx index 5dc3ca8e..31d62580 100644 --- a/source/funkin/editors/charter/CharterEventScreen.hx +++ b/source/funkin/editors/charter/CharterEventScreen.hx @@ -79,13 +79,13 @@ class CharterEventScreen extends UISubstateWindow { else { chartEvent.events = [for (i in eventsList.buttons.members) i.event]; chartEvent.refreshEventIcons(); + Charter.instance.updateBPMEvents(); Charter.undos.addToUndo(CEditEvent(chartEvent, oldEvents, [for (event in events) Reflect.copy(event)])); } } close(); - Charter.instance.updateBPMEvents(); }); saveButton.x -= saveButton.bWidth; add(saveButton); diff --git a/source/funkin/editors/charter/MetaDataScreen.hx b/source/funkin/editors/charter/CharterMetaDataScreen.hx similarity index 99% rename from source/funkin/editors/charter/MetaDataScreen.hx rename to source/funkin/editors/charter/CharterMetaDataScreen.hx index 9ee8783e..900cc721 100644 --- a/source/funkin/editors/charter/MetaDataScreen.hx +++ b/source/funkin/editors/charter/CharterMetaDataScreen.hx @@ -6,7 +6,7 @@ import funkin.editors.extra.PropertyButton; using StringTools; -class MetaDataScreen extends UISubstateWindow { +class CharterMetaDataScreen extends UISubstateWindow { public var metadata:ChartMetaData; public var saveButton:UIButton; public var closeButton:UIButton; diff --git a/source/funkin/editors/charter/CharterNote.hx b/source/funkin/editors/charter/CharterNote.hx index 1ea7804d..1d130842 100644 --- a/source/funkin/editors/charter/CharterNote.hx +++ b/source/funkin/editors/charter/CharterNote.hx @@ -31,7 +31,7 @@ class CharterNote extends UISprite implements ICharterSelectable { public function new() { super(); - + antialiasing = true; ID = -1; loadGraphic(Paths.image('editors/charter/note'), true, 157, 154); animation.add("note", [for(i in 0...frames.frames.length) i], 0, true); @@ -154,7 +154,7 @@ class CharterNote extends UISprite implements ICharterSelectable { if (strumLine != null) { sustainSpr.alpha = alpha = !strumLine.strumLine.visible ? (__passed ? 0.2 : 0.4) : (__passed ? 0.6 : 1); - typeText.alpha = !strumLine.strumLine.visible ? (__passed ? 0.4 : 0.6) : (__passed ? 0.8 : 1); + typeText.alpha = !strumLine.strumLine.visible ? (__passed ? 0.4 : 0.6) : (__passed ? 0.8 : 1); } colorTransform.redMultiplier = colorTransform.greenMultiplier = colorTransform.blueMultiplier = selected ? 0.75 : 1; diff --git a/source/funkin/editors/charter/CharterNoteGroup.hx b/source/funkin/editors/charter/CharterNoteGroup.hx index 78995dca..4c9a1d87 100644 --- a/source/funkin/editors/charter/CharterNoteGroup.hx +++ b/source/funkin/editors/charter/CharterNoteGroup.hx @@ -29,11 +29,11 @@ class CharterNoteGroup extends FlxTypedGroup { for(i in begin...end) { __loopSprite = members[i]; - if (!Charter.selection.contains(__loopSprite)) + if (__loopSprite != null && !Charter.selection.contains(__loopSprite)) noteFunc(__loopSprite); } for(c in Charter.selection.copy()) - if (c is CharterNote) noteFunc(cast (c, CharterNote)); + if (c != null && c is CharterNote) noteFunc(cast (c, CharterNote)); __currentlyLooping = oldCur; } @@ -64,7 +64,7 @@ class CharterNoteGroup extends FlxTypedGroup { if (autoSort && members.length != __lastSort) sortNotes(); - + forEach((n) -> { if(n.exists && n.active) { n.cameras = n.__lastDrawCameras = cameras; diff --git a/source/funkin/editors/charter/CharterNoteHoverer.hx b/source/funkin/editors/charter/CharterNoteHoverer.hx index 46997a9b..e20000a2 100644 --- a/source/funkin/editors/charter/CharterNoteHoverer.hx +++ b/source/funkin/editors/charter/CharterNoteHoverer.hx @@ -44,18 +44,18 @@ class CharterNoteHoverer extends CharterNote { if (Charter.instance.gridActionType == NOTE_DRAG) { var verticalChange:Float = (__mousePos.y - Charter.instance.dragStartPos.y) / 40; var horizontalChange:Int = CoolUtil.floorInt((__mousePos.x - (Std.int(Charter.instance.dragStartPos.x / 40) * 40)) / 40); - + for (s in Charter.selection) { if (s != null && s.draggable && s is CharterNote) { var draggingNote:CharterNote = cast(s, CharterNote); y = (draggingNote.step + verticalChange); - if (!FlxG.keys.pressed.SHIFT) + if (!FlxG.keys.pressed.SHIFT) y -= ((draggingNote.step + verticalChange) - Charter.instance.quantStepRounded(draggingNote.step+verticalChange, verticalChange > 0 ? 0.35 : 0.65)); y *= 40; var newID:Int = Std.int(FlxMath.bound(draggingNote.fullID + horizontalChange, 0, (Charter.instance.strumLines.members.length*4)-1)); x = (id=newID) * 40; y = FlxMath.bound(y, 0, (Charter.instance.__endStep*40) - height); - + angle = switch(animation.curAnim.curFrame = (draggingNote.id % 4)) { case 0: -90; case 1: 180; diff --git a/source/funkin/editors/charter/CharterNoteTypesList.hx b/source/funkin/editors/charter/CharterNoteTypesList.hx index 7eb3abce..b1cc28b6 100644 --- a/source/funkin/editors/charter/CharterNoteTypesList.hx +++ b/source/funkin/editors/charter/CharterNoteTypesList.hx @@ -108,7 +108,7 @@ class NoteTypeButton extends UIButton { var path:String = 'game/notes/default'; if (Assets.exists(Paths.image('game/notes/${notetype}'))) path = 'game/notes/${notetype}'; - + noteSpr.frames = Paths.getFrames(path); noteSpr.animation.addByPrefix('scroll', 'green0'); noteSpr.animation.play("scroll"); diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx index ba1acdc4..6fd97bdc 100644 --- a/source/funkin/editors/charter/CharterSelection.hx +++ b/source/funkin/editors/charter/CharterSelection.hx @@ -29,7 +29,7 @@ class CharterSelection extends EditorTreeMenu { for(s in freeplayList.songs) new EditorIconOption(s.name, "Press ACCEPT to choose a difficulty to edit.", s.icon, function() { curSong = s; var list:Array = [ - for(d in s.difficulties) if (d != "") + for(d in s.difficulties) if (d != "") new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() { FlxG.switchState(new Charter(s.name, d)); }) @@ -46,6 +46,8 @@ class CharterSelection extends EditorTreeMenu { })); main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list); + + DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]); } override function createPost() { @@ -107,7 +109,7 @@ class CharterSelection extends EditorTreeMenu { sys.FileSystem.createDirectory('$songFolder/charts'); // Save Files - sys.io.File.saveContent('$songFolder/meta.json', Json.stringify(creation.meta, "\t")); + CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(creation.meta, "\t")); if (creation.instBytes != null) sys.io.File.saveBytes('$songFolder/song/Inst.${Paths.SOUND_EXT}', creation.instBytes); if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Paths.SOUND_EXT}', creation.voicesBytes); #end @@ -145,9 +147,7 @@ class CharterSelection extends EditorTreeMenu { var songFolder:String = '${Paths.getAssetsRoot()}/songs/${curSong.name}'; // Save Files - #if sys - sys.io.File.saveContent('$songFolder/charts/${name}.json', Json.stringify(data, "\t")); - #end + CoolUtil.safeSaveFile('$songFolder/charts/${name}.json', Json.stringify(data, "\t")); // Add to List curSong.difficulties.push(name); @@ -160,7 +160,7 @@ class CharterSelection extends EditorTreeMenu { var meta = Json.parse(sys.io.File.getContent('$songFolder/meta.json')); if (meta.difficulties != null && !meta.difficulties.contains(name)) { meta.difficulties.push(name); - sys.io.File.saveContent('$songFolder/meta.json', Json.stringify(meta)); + CoolUtil.safeSaveFile('$songFolder/meta.json', Json.stringify(meta)); } } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterStrumLineGroup.hx b/source/funkin/editors/charter/CharterStrumLineGroup.hx index a342c779..483b62ce 100644 --- a/source/funkin/editors/charter/CharterStrumLineGroup.hx +++ b/source/funkin/editors/charter/CharterStrumLineGroup.hx @@ -79,7 +79,7 @@ class CharterStrumLineGroup extends FlxTypedGroup { fixEvents(); } - public inline function fixEvents() { + public inline function fixEvents() { for (i in Charter.instance.eventsGroup.members) { for (j in i.events) { var paramTypes:Array = EventsData.getEventParams(j.name); diff --git a/source/funkin/editors/charter/CharterStrumline.hx b/source/funkin/editors/charter/CharterStrumline.hx index bee6e7a3..3850c2fa 100644 --- a/source/funkin/editors/charter/CharterStrumline.hx +++ b/source/funkin/editors/charter/CharterStrumline.hx @@ -5,6 +5,7 @@ import funkin.game.Character; import funkin.editors.ui.UITopMenu.UITopMenuButton; import funkin.game.HealthIcon; import funkin.backend.chart.ChartData.ChartStrumLine; +import funkin.backend.shaders.CustomShader; import flixel.sound.FlxSound; class CharterStrumline extends UISprite { @@ -22,6 +23,17 @@ class CharterStrumline extends UISprite { public var vocals:FlxSound; + public var selectedWaveform(default, set):Int = -1; + public function set_selectedWaveform(value:Int):Int { + if (value == -1) waveformShader = null; + else { + var shaderName:String = Charter.waveformHandler.waveformList[value]; + waveformShader = Charter.waveformHandler.waveShaders.get(shaderName); + } + return selectedWaveform = value; + } + public var waveformShader:CustomShader; + public function new(strumLine:ChartStrumLine) { super(); this.strumLine = strumLine; @@ -60,6 +72,8 @@ class CharterStrumline extends UISprite { vocals = strumLine.vocalsSuffix.length > 0 ? FlxG.sound.load(Paths.voices(PlayState.SONG.meta.name, PlayState.difficulty, strumLine.vocalsSuffix)) : new FlxSound(); vocals.group = FlxG.sound.defaultMusicGroup; + + selectedWaveform = -1; } private var __healthYOffset:Float = 0; @@ -154,6 +168,22 @@ class CharterStrumlineOptions extends UITopMenuButton { icon: 3 } ]; + + contextMenu.insert(0, { + label: "No Waveform", + onSelect: function(_) {strLine.selectedWaveform = -1;}, + icon: strLine.selectedWaveform == -1 ? 1 : 0 + }); + + for (i => name in Charter.waveformHandler.waveformList) + contextMenu.insert(1+i, { + label: name, + onSelect: function(_) {strLine.selectedWaveform = i;}, + icon: strLine.selectedWaveform == i ? 6 : 5 + }); + + contextMenu.insert(1+Charter.waveformHandler.waveformList.length, null); + super.openContextMenu(); } } \ No newline at end of file diff --git a/source/funkin/editors/charter/CharterStrumlineButton.hx b/source/funkin/editors/charter/CharterStrumlineButton.hx index 2a6fd184..c5619330 100644 --- a/source/funkin/editors/charter/CharterStrumlineButton.hx +++ b/source/funkin/editors/charter/CharterStrumlineButton.hx @@ -59,10 +59,10 @@ class CharterStrumlineButton extends UISprite { super.update(elapsed); UIState.state.updateSpriteRect(button); - if(UIState.state.isOverlapping(button, button.__rect)) { + if(UIState.state.curContextMenu == null && UIState.state.isOverlapping(button, button.__rect)) { buttonScale.set(0.95, 0.95); - if (FlxG.mouse.justReleased && onClick != null) { - onClick(); + if (FlxG.mouse.justPressed && onClick != null) { + onClick(); if (animationOnClick) pressAnimation(); } } @@ -96,7 +96,7 @@ class CharterStrumlineButton extends UISprite { scaleTween = FlxTween.tween(buttonScaleOffset, {x: .015 * (extra ? 3 : 1), y: .02 * (extra ? 3 : 1)}, .12, {ease: FlxEase.circOut}) .then(FlxTween.tween(buttonScaleOffset, {x: 0, y: 0}, .1 + FlxG.random.float(.1, .3), {ease: FlxEase.circIn})); - if (!extra) return; + if (!extra) return; if (angleTween != null) angleTween.cancel(); button.angle = 0; angleTween = FlxTween.tween(button, {angle: 360}, .5, {ease: FlxEase.circInOut}); diff --git a/source/funkin/editors/charter/CharterStrumlineScreen.hx b/source/funkin/editors/charter/CharterStrumlineScreen.hx index 5af1b606..26696251 100644 --- a/source/funkin/editors/charter/CharterStrumlineScreen.hx +++ b/source/funkin/editors/charter/CharterStrumlineScreen.hx @@ -63,7 +63,7 @@ class CharterStrumlineScreen extends UISubstateWindow { var charFileList = Character.getList(true); if (charFileList.length == 0) charFileList = Character.getList(false); - + charactersList = new UIButtonList(15, title.y + title.height + 36, 250, 269, "", FlxPoint.get(250, 54), null, 0); charactersList.addButton.callback = () -> charactersList.add(new CharacterButton(0, 0, "New Char", charFileList, charactersList)); charactersList.cameraSpacing = 0; @@ -112,7 +112,7 @@ class CharterStrumlineScreen extends UISubstateWindow { var strOffset:Float = strumLine.strumLinePos == null ? (strumLine.type == 1 ? 0.75 : 0.25) : strumLine.strumLinePos; - var startingPos:FlxPoint = strumLine.strumLinePos == null ? + var startingPos:FlxPoint = strumLine.strumPos == null ? FlxPoint.get(strOffset, 50) : FlxPoint.get(strOffset, strumLine.strumPos[1]); @@ -142,7 +142,7 @@ class CharterStrumlineScreen extends UISubstateWindow { closeButton.color = 0xFFFF0000; var suffixlist = ["NONE"]; - for (i in Paths.getFolderContent('songs/${Charter.__song.toLowerCase()}/song')) + for (i in Paths.getFolderContent('songs/${Charter.__song.toLowerCase()}/song')) if (i.startsWith("Voices")) { i = haxe.io.Path.withoutExtension(i.substr("Voices".length)); if (i == "") continue; diff --git a/source/funkin/editors/charter/CharterWaveformHandler.hx b/source/funkin/editors/charter/CharterWaveformHandler.hx new file mode 100644 index 00000000..44ef5ee9 --- /dev/null +++ b/source/funkin/editors/charter/CharterWaveformHandler.hx @@ -0,0 +1,110 @@ +package funkin.editors.charter; + +import flixel.util.FlxColor; +import funkin.backend.system.Conductor; +import flixel.sound.FlxSound; +import funkin.backend.shaders.CustomShader; +import openfl.display.BitmapData; + +class CharterWaveformHandler extends FlxBasic { + public var ampsNeeded:Float = 0; + public var ampSqrt(get, never):Int; + public function get_ampSqrt():Int { + var sqrt:Float = Math.sqrt(Math.floor(ampsNeeded/3)); + var ampRet:Int = Math.floor(sqrt); + if (sqrt % 1 > 0) ampRet += 1; + return ampRet; + } + + public var waveDatas:Map = []; + public var waveShaders:Map = []; + + public var sounds:Map = []; + public var analyzers:Map = []; + + public var waveformList:Array = []; + + public function new() {super(); exists = false;} + + public function generateData(name:String, sound:FlxSound):BitmapData { + if (!sounds.exists(name)) sounds.set(name, sound); + if (!analyzers.exists(name)) + analyzers.set(name, new AudioAnalyzer(sound)); + + var analyzer:AudioAnalyzer = analyzers.get(name); + + var pixelsNeeded:Int = Math.floor(ampsNeeded/3); + if ((ampsNeeded/3) % 1 > 0) pixelsNeeded += 1; + + var waveData:BitmapData = new BitmapData( + ampSqrt, 1+Math.floor(pixelsNeeded/ampSqrt), true, 0xFF000000 + ); + + for (y in 0...waveData.height) + for (x in 0...waveData.width) { + var amplitudes:Array = [0., 0., 0.]; + for (color in 0...3) { + var gridY:Float = (y * (waveData.width * 3)) + (x * 3) + color; + + var startTime:Float = Conductor.getTimeForStep(gridY/40); + if (startTime > sound.length) if (color == 0) break; else continue; + + var endTime:Float = Conductor.getTimeForStep((gridY+1)/40); + if (endTime > sound.length) if (color == 0) break; else continue; + + var amplitude:Float = analyzer.analyze(startTime, endTime); + amplitudes[color] = amplitude; + } + waveData.setPixel(x, y, FlxColor.fromRGBFloat(amplitudes[0], amplitudes[1], amplitudes[2])); + } + + waveDatas.set(name, waveData); + return waveData; + } + + public function generateShader(name:String, sound:FlxSound):CustomShader { + if (!waveDatas.exists(name)) generateData(name, sound); + + var waveData:BitmapData = waveDatas.get(name); + + var waveShader:CustomShader = new CustomShader("engine/editorWaveforms"); + waveShader.data.waveformSize.value = [waveData.width, waveData.height]; + waveShader.data.waveformTexture.input = waveData; + waveShader.data.textureRes.value = [0, 0]; + waveShader.data.pixelOffset.value = [0]; + waveShader.data.lowDetail.value = [Options.charterLowDetailWaveforms]; + + waveShaders.set(name, waveShader); + return waveShader; + } + + public function clearWaveform(name:String) { + waveDatas.get(name).dispose(); + waveDatas.remove(name); + + waveShaders.set(name, null); + waveShaders.remove(name); + + analyzers.set(name, null); + analyzers.remove(name); + + sounds.remove(name); + waveformList.remove(name); + } + + public function clearWaveforms() { + for (data in waveDatas) data.dispose(); + for (shader in waveShaders) shader = null; + for (analyzer in analyzers) analyzer = null; + + waveDatas.clear(); waveShaders.clear(); + analyzers.clear(); sounds.clear(); + + waveformList = []; + } + + public override function destroy() { + clearWaveforms(); + super.destroy(); + } +} \ No newline at end of file diff --git a/source/funkin/editors/ui/UIAudioPlayer.hx b/source/funkin/editors/ui/UIAudioPlayer.hx index ad1ba457..b6b95448 100644 --- a/source/funkin/editors/ui/UIAudioPlayer.hx +++ b/source/funkin/editors/ui/UIAudioPlayer.hx @@ -87,7 +87,7 @@ class UIAudioPlayer extends UIButton { playingSprite.animation.play(sound.playing ? "playing" : "paused"); timeText.text = '${FlxStringUtil.formatTime(sound.time/1000, true)} / ${FlxStringUtil.formatTime(sound.length/1000)}'; - if(timeBarPlayer.clipRect == null) + if(timeBarPlayer.clipRect == null) timeBarPlayer.clipRect = new FlxRect(0, 0, timeBarPlayer.frameWidth, timeBarPlayer.frameHeight); timeBarPlayer.clipRect.x = timeBarPlayer.frameWidth * (sound.time/sound.length); @@ -106,13 +106,13 @@ class UIAudioPlayer extends UIButton { var spritePos:FlxPoint = sprite.getScreenPosition(FlxPoint.get(), __lastDrawCameras[0]); - if (((mousePos.x > (spritePos.x)) && (mousePos.x < (spritePos.x) + timeBar.barWidth)) + if (((mousePos.x > (spritePos.x)) && (mousePos.x < (spritePos.x) + timeBar.barWidth)) && ((mousePos.y > (spritePos.y)) && (mousePos.y < (spritePos.y) + timeBar.barHeight))) { if (FlxG.mouse.justPressed) { draggingObj = sprite; wasPlaying = draggingObj == timeBar ? sound.playing : false; } - + if (FlxG.mouse.pressed) { if (draggingObj == timeBar) { if (sound.playing) sound.pause(); @@ -137,7 +137,7 @@ class UIAudioPlayer extends UIButton { super.destroy(); sound.stop(); - @:privateAccess sound.reset(); + @:privateAccess sound.reset(); bytes = null; } } \ No newline at end of file diff --git a/source/funkin/editors/ui/UIButtonList.hx b/source/funkin/editors/ui/UIButtonList.hx index 59d88971..f6fbc3a1 100644 --- a/source/funkin/editors/ui/UIButtonList.hx +++ b/source/funkin/editors/ui/UIButtonList.hx @@ -121,7 +121,6 @@ class UIButtonList extends UIWindow { if (dragCallback != null) dragCallback(cast button, button.ID, i); button.ID = i; // Ok back to normal :D } - } addButton.selectable = (hovered && !dragging); diff --git a/source/funkin/editors/ui/UIColorwheel.hx b/source/funkin/editors/ui/UIColorwheel.hx index a25b41bd..d19db808 100644 --- a/source/funkin/editors/ui/UIColorwheel.hx +++ b/source/funkin/editors/ui/UIColorwheel.hx @@ -87,11 +87,11 @@ class UIColorwheel extends UISliceSprite { for (item in [colorPicker, colorPickerSelector, colorSlider, colorSliderSelector, colorHexTextBox, hexLabel, rgbLabel]) members.push(item); - updateWheel(); + updateWheel(false); } inline function updateColorPickerSelector() - colorPickerSelector.selector.setPosition(colorPicker.x + (colorPicker.width *saturation) - 8, colorPicker.y + (colorPicker.height + (colorPicker.height * -brightness))- 8); + colorPickerSelector.selector.setPosition(colorPicker.x + (colorPicker.width *saturation) - 8, colorPicker.y + (colorPicker.height + (colorPicker.height * -brightness))- 8); inline function updateColorPickerMouse(mousePos:FlxPoint) { saturation = mousePos.x/colorPicker.width; brightness = 1 + -(mousePos.y/colorPicker.height); @@ -103,12 +103,13 @@ class UIColorwheel extends UISliceSprite { inline function updateColorSliderMouse(mousePos:FlxPoint) hue = (mousePos.y / colorSlider.height) * 360; - public function updateWheel() { + public function updateWheel(checkChanged:Bool = true) { + if(checkChanged) colorChanged = true; colorPickerShader.hset("hue", hue / 360); colorPickerSelector.curColor = color = curColor = FlxColor.fromHSB(hue, saturation, brightness); colorSliderSelector.curColor = FlxColor.fromHSB(hue, 1, 1); updateColorPickerSelector(); updateColorSliderPickerSelector(); - colorHexTextBox.label.text = curColorString = curColor.toHexString(false).replace("0x", "#"); + colorHexTextBox.label.text = curColorString = curColor.toWebString(); for (numStepper in rgbNumSteppers) { numStepper.label.text = Std.string(switch (numStepper.ID) { default: curColor.red; @@ -121,7 +122,7 @@ class UIColorwheel extends UISliceSprite { // For Character Editor public var colorChanged:Bool = false; - // Make the colorwhell feel better + // Make the colorwheel feel better static inline var hitBoxExtenstion:Float = 8; public override function update(elapsed:Float) { @@ -138,7 +139,6 @@ class UIColorwheel extends UISliceSprite { if (sprite == colorSlider) updateColorSliderMouse(mousePos); if (sprite == colorPicker) updateColorPickerMouse(mousePos); updateWheel(); - colorChanged = true; spritePos.put(); break; diff --git a/source/funkin/editors/ui/UISliceSprite.hx b/source/funkin/editors/ui/UISliceSprite.hx index 9556a521..09226205 100644 --- a/source/funkin/editors/ui/UISliceSprite.hx +++ b/source/funkin/editors/ui/UISliceSprite.hx @@ -55,7 +55,7 @@ class UISliceSprite extends UISprite { frame = topleft; setPosition(x, y); __setSize( - topleft.frame.width * Math.min(bWidth/(topleft.frame.width*2), 1), + topleft.frame.width * Math.min(bWidth/(topleft.frame.width*2), 1), topleft.frame.height * Math.min(bHeight/(topleft.frame.height*2), 1) ); super.drawSuper(); @@ -72,7 +72,7 @@ class UISliceSprite extends UISprite { setPosition(x + bWidth - (topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1)), y); frame = topright; __setSize( - topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1), + topright.frame.width * Math.min(bWidth/(topright.frame.width*2), 1), topright.frame.height * Math.min(bHeight/(topright.frame.height*2), 1) ); super.drawSuper(); @@ -110,7 +110,7 @@ class UISliceSprite extends UISprite { frame = bottomleft; setPosition(x, y + bHeight - (bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1))); __setSize( - bottomleft.frame.width * Math.min(bWidth/(bottomleft.frame.width*2), 1), + bottomleft.frame.width * Math.min(bWidth/(bottomleft.frame.width*2), 1), bottomleft.frame.height * Math.min(bHeight/(bottomleft.frame.height*2), 1) ); super.drawSuper(); @@ -126,11 +126,11 @@ class UISliceSprite extends UISprite { // BOTTOM RIGHT frame = bottomright; setPosition( - x + bWidth - (bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1)), + x + bWidth - (bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1)), y + bHeight - (bottomright.frame.height * Math.min(bHeight/(bottomright.frame.height*2), 1)) ); __setSize( - bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1), + bottomright.frame.width * Math.min(bWidth/(bottomright.frame.width*2), 1), bottomright.frame.height * Math.min(bHeight/(bottomright.frame.height*2), 1) ); super.drawSuper(); diff --git a/source/funkin/editors/ui/UISlider.hx b/source/funkin/editors/ui/UISlider.hx index 8c5a4537..277c7b95 100644 --- a/source/funkin/editors/ui/UISlider.hx +++ b/source/funkin/editors/ui/UISlider.hx @@ -43,21 +43,21 @@ class UISlider extends UISprite { makeGraphic(barWidth, 12, 0x00000000, true); this.drawRoundRect(0, 0, barWidth, 12, 8, 6, 0xFFC0C0C0); - this.drawRoundRect(1, 1, barWidth-2, 12-2, 8, 5, 0xFF140013); + this.drawRoundRect(1, 1, barWidth-2, 12-2, 8, 5, 0xFF140013); cursor = BUTTON; - progressbar = new UISprite(centered ? barWidth/2 : 0); + progressbar = new UISprite(centered ? barWidth/2 : 0); progressbar.makeGraphic(barWidth, 8, 0x00000000, true); progressbar.drawRoundRect(2, 0, barWidth-4, 8, 8, 2, 0xFFFFFFFF); - progressbar.origin.x = 0; progressbar.colorTransform.color = 0xFF67009B; - members.push(progressbar); + progressbar.origin.x = 0; progressbar.colorTransform.color = 0xFF67009B; + members.push(progressbar); this.value = value; visualProgress = __barProgress; members.push(startText = new UIText(x, y, 0, Std.string(segments[0].start).replace("0.", "."))); members.push(endText = new UIText(x + (barWidth) + 8, y, 0, Std.string(segments[segments.length-1].end).replace("0.", "."))); - + for (i in 0...2) { var selectableBar:UISprite = new UISprite(x,y); selectableBar.loadGraphic(Paths.image("editors/ui/slider")); @@ -91,14 +91,14 @@ class UISlider extends UISprite { var visualProgress:Float = 0; var __stepperWidth = 25; - + public override function update(elapsed:Float) { selectableHitbox.follow(this, 0, (height-selectableHitbox.height)/2); __stepperWidth = valueStepper.label.text.length <= 0 ? 25 : Std.int(valueStepper.label.textField.width) + 12; valueStepper.bWidth = Std.int(FlxMath.lerp(valueStepper.bWidth, __stepperWidth, 1/2.25)); valueStepper.follow(this, -startText.width-10 - valueStepper.bWidth - 4, (height-valueStepper.bHeight)/2); - + var lastBarProgress:Float = __barProgress; if (selectableHitbox.hovered && FlxG.mouse.pressed) { @@ -111,13 +111,13 @@ class UISlider extends UISprite { if (onChange != null) onChange(@:bypassAccessor value = __calcValue(__barProgress)); valueStepper.value = value; } - + visualProgress = FlxMath.lerp(visualProgress, __barProgress, 1/2.25); progressbar.follow(this, progressCentered ? barWidth/2 : 0, (height-progressbar.height)/2); progressbar.scale.x = FlxMath.bound(visualProgress-(progressCentered?0.5:0),-1,1); progressbar.colorTransform.color = FlxColor.interpolate(progressbar.colorTransform.color, selectableHitbox.hovered ? 0xFF7F00BF : 0xFF67009B, 1/14); - selectableBar.follow(this, (visualProgress * barWidth) - (selectableBar.width/2), (height-selectableBar.height)/2); selectableBarHighlight.follow(selectableBar); + selectableBar.follow(this, (visualProgress * barWidth) - (selectableBar.width/2), (height-selectableBar.height)/2); selectableBarHighlight.follow(selectableBar); selectableBarHighlight.alpha = FlxMath.lerp(selectableBarHighlight.alpha, selectableHitbox.hovered ? 0.1: 0, 1/14); startText.follow(this, -startText.width-10, (height/2) - (startText.height/2)); @@ -128,13 +128,13 @@ class UISlider extends UISprite { private function __calcValue(progress:Float):Float { var totalProgress:Float = 0; - for (segment in segments) { - if (progress < totalProgress + segment.size) { - var relativeProgress = (progress - totalProgress) / segment.size; - return FlxMath.remapToRange(relativeProgress, 0, 1, segment.start, segment.end); - } - totalProgress += segment.size; - } + for (segment in segments) { + if (progress < totalProgress + segment.size) { + var relativeProgress = (progress - totalProgress) / segment.size; + return FlxMath.remapToRange(relativeProgress, 0, 1, segment.start, segment.end); + } + totalProgress += segment.size; + } return segments[segments.length-1].end; } @@ -142,14 +142,14 @@ class UISlider extends UISprite { if (value >= segments[segments.length-1].end) return 1; if (value <= segments[0].start) return 0; - var totalProgress:Float = 0; - for (segment in segments) { - if (value >= segment.start && value <= segment.end) { - var relativeProgress = FlxMath.remapToRange(value, segment.start, segment.end, 0, 1); - return totalProgress + relativeProgress * segment.size; - } - totalProgress += segment.size; - } + var totalProgress:Float = 0; + for (segment in segments) { + if (value >= segment.start && value <= segment.end) { + var relativeProgress = FlxMath.remapToRange(value, segment.start, segment.end, 0, 1); + return totalProgress + relativeProgress * segment.size; + } + totalProgress += segment.size; + } return -1; } } \ No newline at end of file diff --git a/source/funkin/game/Character.hx b/source/funkin/game/Character.hx index 569b58dc..68cfa627 100644 --- a/source/funkin/game/Character.hx +++ b/source/funkin/game/Character.hx @@ -1,6 +1,5 @@ package funkin.game; -import haxe.xml.Printer; import flixel.util.FlxColor; import funkin.backend.FunkinSprite; import flixel.graphics.frames.FlxFrame; @@ -8,8 +7,6 @@ import flixel.math.FlxPoint; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.system.interfaces.IOffsetCompatible; import funkin.backend.utils.XMLUtil; -import flixel.animation.FlxBaseAnimation; -import flixel.graphics.frames.FlxAtlasFrames; import flixel.math.FlxRect; import haxe.xml.Access; import haxe.Exception; @@ -35,12 +32,6 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset public var stunned(default, set):Bool = false; - private function set_stunned(b:Bool) - { - __stunnedTime = 0; - return stunned = b; - } - public var isPlayer:Bool = false; public var isGF:Bool = false; public var curCharacter:String = 'bf'; @@ -80,10 +71,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset { var anims = ["singLEFT", "singDOWN", "singUP", "singRIGHT"]; - var event = EventManager.get(DirectionAnimEvent).recycle('${anims[direction]}$suffix', direction, suffix, Context, Reversed, Frame, Force); + var event = EventManager.get(DirectionAnimEvent).recycle(anims[direction] + suffix, direction, suffix, Context, Reversed, Frame, Force); script.call("onPlaySingAnim", [event]); if (!event.cancelled) - playAnim(event.animName, event.force, Context, event.reversed, event.frame); + playAnim(event.animName, event.force, event.context, event.reversed, event.frame); } public function new(x:Float, y:Float, ?character:String = "bf", isPlayer:Bool = false, switchAnims:Bool = true) @@ -157,9 +148,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset script.call("postCreate"); } - public function fixChar(switchAnims:Bool = false) + public function fixChar(switchAnims:Bool = false, autoInterval:Bool = false) { - isDanceLeftDanceRight = (hasAnimation("danceLeft") && hasAnimation("danceRight")); + if (isDanceLeftDanceRight = (hasAnimation("danceLeft") && hasAnimation("danceRight")) && autoInterval) + beatInterval = 1; // alternative to xor operator // for people who dont believe it, heres the truth table @@ -197,6 +189,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (__stunnedTime > 5 / 60) stunned = false; } + + if (!__lockAnimThisFrame && lastAnimContext != DANCE) + tryDance(); + __lockAnimThisFrame = false; } @@ -207,24 +203,23 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset */ public function dance() { - if (!debugMode) - { - var event = EventManager.get(DanceEvent).recycle(danced); - script.call("onDance", [event]); - if (event.cancelled) - return; + if(debugMode) return; - switch (curCharacter) - { - // hardcode custom dance animations here - default: - if (isDanceLeftDanceRight) - { - playAnim(((danced = !danced) ? 'danceLeft' : 'danceRight') + idleSuffix, DANCE); - } - else - playAnim('idle' + idleSuffix, DANCE); - } + var event = EventManager.get(DanceEvent).recycle(danced); + script.call("onDance", [event]); + if (event.cancelled) + return; + + switch (curCharacter) + { + // hardcode custom dance animations here + default: + if (isDanceLeftDanceRight) + { + playAnim(((danced = !danced) ? 'danceLeft' : 'danceRight') + idleSuffix, DANCE); + } + else + playAnim('idle' + idleSuffix, DANCE); } } @@ -250,17 +245,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset */ public var danceOnBeat:Bool = true; - /** - * Interval at which the character will dance (higher number = slower dance) - */ - public var danceInterval:Int = 1; - public override function beatHit(curBeat:Int) { script.call("beatHit", [curBeat]); - if (danceInterval < 1) - danceInterval = 1; - if (danceOnBeat && curBeat % danceInterval == 0 && !__lockAnimThisFrame) + if (danceOnBeat && (curBeat + beatOffset) % beatInterval == 0 && !__lockAnimThisFrame) { tryDance(); } @@ -415,7 +403,10 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset if (xml.x.exists("antialiasing")) antialiasing = (xml.x.get("antialiasing") == "true"); if (xml.x.exists("sprite")) sprite = xml.x.get("sprite"); - var tempList = ["isPlayer", "isGF", "x", "y", "gameOverChar", "camx", "camy", "holdTime", "flipX", "icon", "color", "scale", "antialiasing", "sprite"]; + var hasInterval:Bool = xml.x.exists("interval"); + if (hasInterval) beatInterval = Std.parseInt(xml.x.get("interval")); + + var tempList = ["isPlayer", "isGF", "x", "y", "gameOverChar", "camx", "camy", "holdTime", "flipX", "icon", "color", "scale", "antialiasing", "sprite", "interval"]; var atts = [for (i in xml.x.attributes()) i]; for (i in atts) if (!tempList.contains(i)) { @@ -431,7 +422,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset XMLUtil.addXMLAnimation(this, anim); } - fixChar(true); + fixChar(true, !hasInterval); dance(); } @@ -448,7 +439,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset xml.set("flipX", Std.string(flipX)); xml.set("icon", getIcon()); if (iconColor != null) - xml.set("color", iconColor.toHexString(false).replace("0x", "#")); + xml.set("color", iconColor.toWebString()); xml.set("scale", Std.string(scale.x)); xml.set("antialiasing", antialiasing == true ? "true" : "false"); xml.set("sprite", sprite); @@ -474,6 +465,7 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset var offset:FlxPoint = getAnimOffset(anim.name); animXml.set("x", Std.string(offset.x)); animXml.set("y", Std.string(offset.y)); + offset.putWeak(); if (anim.indices.length > 0) animXml.set("indices", anim.indices.join(",")); xml.addChild(animXml); @@ -490,6 +482,25 @@ class Character extends FunkinSprite implements IBeatReceiver implements IOffset return [for(a in xml.nodes.anim) if(a.has.name) a.att.name]; } + // Getters / Setters + + @:noCompletion private function set_stunned(b:Bool) + { + __stunnedTime = 0; + return stunned = b; + } + + // Backwards Compat + + /** + * Interval at which the character will dance (higher number = slower dance) + */ + @:noCompletion public var danceInterval(get, set):Int; + @:noCompletion private function set_danceInterval(v:Int) + return beatInterval = v; + @:noCompletion private function get_danceInterval() + return beatInterval; + // Statics public static function getIconFromCharName(?curCharacter:String) { diff --git a/source/funkin/game/GameOverSubstate.hx b/source/funkin/game/GameOverSubstate.hx index 3d119f02..4868b130 100644 --- a/source/funkin/game/GameOverSubstate.hx +++ b/source/funkin/game/GameOverSubstate.hx @@ -87,8 +87,7 @@ class GameOverSubstate extends MusicBeatSubstate lossSFX = FlxG.sound.play(Paths.sound(lossSFXName)); Conductor.changeBPM(gameOverSongBPM); - DiscordUtil.changePresence('Game Over', PlayState.SONG.meta.displayName + " (" + PlayState.difficulty + ")"); - + DiscordUtil.call("onGameOver", []); gameoverScript.call("postCreate"); } diff --git a/source/funkin/game/HealthIcon.hx b/source/funkin/game/HealthIcon.hx index 9fd88170..d55bf14b 100644 --- a/source/funkin/game/HealthIcon.hx +++ b/source/funkin/game/HealthIcon.hx @@ -8,14 +8,21 @@ class HealthIcon extends FlxSprite */ public var sprTracker:FlxSprite; + /** + * The currently showing icon + */ + public var curCharacter:String = null; + + /** + * If the character is for the player + */ + public var isPlayer:Bool; + /** * Health steps in this format: * Min Percentage => Frame Index */ - public var healthSteps:Map = [ - 0 => 1, // losing icon - 20 => 0, // normal icon - ]; + public var healthSteps:Map = null; /** * Helper for HScript who can't make maps @@ -40,21 +47,34 @@ class HealthIcon extends FlxSprite { super(); health = 0.5; - var path = Paths.image('icons/$char'); - if (!Assets.exists(path)) path = Paths.image('icons/face'); - - loadGraphic(path, true, 150, 150); - - animation.add(char, [for(i in 0...frames.frames.length) i], 0, false, isPlayer); - antialiasing = true; - animation.play(char); - - if (frames.frames.length >= 3) - healthSteps[80] = 2; // winning icon + this.isPlayer = isPlayer; + setIcon(char); scrollFactor.set(); } + public function setIcon(char:String, width:Int = 150, height:Int = 150) { + if(curCharacter != char || this.width != width || this.height != height) { + curCharacter = char; + var path = Paths.image('icons/$char'); + if (!Assets.exists(path)) path = Paths.image('icons/face'); + + loadGraphic(path, true, width, height); + + animation.add(char, [for(i in 0...frames.frames.length) i], 0, false, isPlayer); + antialiasing = true; + animation.play(char); + + healthSteps = [ + 0 => 1, // losing icon + 20 => 0, // normal icon + ]; + + if (frames.frames.length >= 3) + healthSteps[80] = 2; // winning icon + } + } + override function update(elapsed:Float) { super.update(elapsed); diff --git a/source/funkin/game/NoteGroup.hx b/source/funkin/game/NoteGroup.hx index 97b34da6..0d45a41f 100644 --- a/source/funkin/game/NoteGroup.hx +++ b/source/funkin/game/NoteGroup.hx @@ -106,6 +106,7 @@ class NoteGroup extends FlxTypedGroup { if (members == null) return null; + // TODO: maybe lastIndexOf var index:Int = members.indexOfFromLast(Object); if (index < 0) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index c567b3c5..2ae26370 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -649,7 +649,7 @@ class PlayState extends MusicBeatState var strLine = new StrumLine(chars, startingPos, strumLine.strumScale == null ? 1 : strumLine.strumScale, - strumLine.type == 2 || (!coopMode && !((strumLine.type == 1 && !opponentMode) || (strumLine.type == 0 && opponentMode))), + strumLine.type == 2 || (!coopMode && !((strumLine.type == 1 && !opponentMode) || (strumLine.type == 0 && opponentMode))), strumLine.type != 1, coopMode ? (strumLine.type == 1 ? controlsP1 : controlsP2) : controls, strumLine.vocalsSuffix ); @@ -758,8 +758,8 @@ class PlayState extends MusicBeatState SaveWarning.selectionClass = CharterSelection; SaveWarning.warningFunc = saveWarn; SaveWarning.saveFunc = () -> { - @:privateAccess Chart.save('${Paths.getAssetsRoot()}/songs/${Charter.__song.toLowerCase()}', - PlayState.SONG, Charter.__diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: true}); + @:privateAccess Chart.save('${Paths.getAssetsRoot()}/songs/${Charter.__song.toLowerCase()}', + PlayState.SONG, Charter.__diff.toLowerCase(), {saveMetaInChart: false, saveEventsInChart: true, prettyPrint: Options.editorPrettyPrint}); } } } @@ -784,7 +784,7 @@ class PlayState extends MusicBeatState * This function is dynamic, which means you can do `updateDiscordPresence = function() {}` in scripts. */ public dynamic function updateDiscordPresence() - DiscordUtil.changeSongPresence(detailsText, (paused ? "Paused - " : "") + SONG.meta.displayName + " (" + difficulty + ")", inst, getIconRPC()); + DiscordUtil.call("onPlayStateUpdate", []); /** * Starts a cutscene. @@ -1846,10 +1846,10 @@ class PlayState extends MusicBeatState private inline function get_playerStrums():StrumLine return strumLines.members[1]; private inline function get_gfSpeed():Int - return (strumLines.members[2] != null && strumLines.members[2].characters[0] != null) ? strumLines.members[2].characters[0].danceInterval : 1; + return (strumLines.members[2] != null && strumLines.members[2].characters[0] != null) ? strumLines.members[2].characters[0].beatInterval : 1; private inline function set_gfSpeed(v:Int):Int { if (strumLines.members[2] != null && strumLines.members[2].characters[0] != null) - strumLines.members[2].characters[0].danceInterval = v; + strumLines.members[2].characters[0].beatInterval = v; return v; } diff --git a/source/funkin/game/cutscenes/ScriptedCutscene.hx b/source/funkin/game/cutscenes/ScriptedCutscene.hx index 522ad668..3f36f1af 100644 --- a/source/funkin/game/cutscenes/ScriptedCutscene.hx +++ b/source/funkin/game/cutscenes/ScriptedCutscene.hx @@ -51,7 +51,7 @@ class ScriptedCutscene extends Cutscene { script.call("destroy"); super.destroy(); } - + // VIDEOS #if REGION public function startVideo(path:String, ?callback:Void->Void) { diff --git a/source/funkin/menus/BetaWarningState.hx b/source/funkin/menus/BetaWarningState.hx index 08d60f39..16201454 100644 --- a/source/funkin/menus/BetaWarningState.hx +++ b/source/funkin/menus/BetaWarningState.hx @@ -29,6 +29,8 @@ class BetaWarningState extends MusicBeatState { var off = Std.int((FlxG.height - (disclaimer.y + disclaimer.height)) / 2); disclaimer.y += off; titleAlphabet.y += off; + + DiscordUtil.call("onMenuLoaded", ["Beta Warning"]); } public override function update(elapsed:Float) { diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index f88b8d99..7ca66b02 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -116,10 +116,10 @@ class FreeplayState extends MusicBeatState } } - DiscordUtil.changePresence("In the Menus", null); + DiscordUtil.call("onMenuLoaded", ["Freeplay"]); super.create(); - + // LOAD CHARACTERS bg = new FlxSprite(0, 0).loadAnimatedGraphic(Paths.image('menus/menuDesat')); diff --git a/source/funkin/menus/GitarooPause.hx b/source/funkin/menus/GitarooPause.hx index a8448b4e..cff8aec9 100644 --- a/source/funkin/menus/GitarooPause.hx +++ b/source/funkin/menus/GitarooPause.hx @@ -19,25 +19,25 @@ class GitarooPause extends MusicBeatState if (FlxG.sound.music != null) FlxG.sound.music.stop(); - var bg:FlxSprite = new FlxSprite().loadAnimatedGraphic(Paths.image('menus/pauseAlt//pauseBG')); + var bg:FlxSprite = new FlxSprite().loadAnimatedGraphic(Paths.image('menus/pauseAlt/pauseBG')); add(bg); var bf:FlxSprite = new FlxSprite(0, 30); - bf.frames = Paths.getFrames('menus/pauseAlt//bfLol'); + bf.frames = Paths.getFrames('menus/pauseAlt/bfLol'); bf.animation.addByPrefix('lol', "funnyThing", 13); bf.animation.play('lol'); add(bf); bf.screenCenter(X); replayButton = new FlxSprite(FlxG.width * 0.28, FlxG.height * 0.7); - replayButton.frames = Paths.getFrames('menus/pauseAlt//pauseUI'); + replayButton.frames = Paths.getFrames('menus/pauseAlt/pauseUI'); replayButton.animation.addByPrefix('selected', 'bluereplay', 0, false); replayButton.animation.appendByPrefix('selected', 'yellowreplay'); replayButton.animation.play('selected'); add(replayButton); cancelButton = new FlxSprite(FlxG.width * 0.58, replayButton.y); - cancelButton.frames = Paths.getFrames('menus/pauseAlt//pauseUI'); + cancelButton.frames = Paths.getFrames('menus/pauseAlt/pauseUI'); cancelButton.animation.addByPrefix('selected', 'bluecancel', 0, false); cancelButton.animation.appendByPrefix('selected', 'cancelyellow'); cancelButton.animation.play('selected'); @@ -55,7 +55,7 @@ class GitarooPause extends MusicBeatState if (controls.ACCEPT) { - if (PlayState.chartingMode && Charter.undos.unsaved) + if (PlayState.instance != null && PlayState.chartingMode && Charter.undos.unsaved) PlayState.instance.saveWarn(false); else { if (replaySelect) diff --git a/source/funkin/menus/MainMenuState.hx b/source/funkin/menus/MainMenuState.hx index 2c5c7101..b17e9db6 100644 --- a/source/funkin/menus/MainMenuState.hx +++ b/source/funkin/menus/MainMenuState.hx @@ -32,7 +32,7 @@ class MainMenuState extends MusicBeatState { super.create(); - DiscordUtil.changePresence("In the Menus", null); + DiscordUtil.call("onMenuLoaded", ["Main Menu"]); CoolUtil.playMenuSong(); @@ -101,9 +101,7 @@ class MainMenuState extends MusicBeatState if (FlxG.keys.justPressed.SEVEN) FlxG.switchState(new funkin.desktop.DesktopMain()); if (FlxG.keys.justPressed.EIGHT) { - #if sys - sys.io.File.saveContent("chart.json", Json.stringify(funkin.backend.chart.Chart.parse("dadbattle", "hard"))); - #end + CoolUtil.safeSaveFile("chart.json", Json.stringify(funkin.backend.chart.Chart.parse("dadbattle", "hard"))); } */ } @@ -118,7 +116,6 @@ class MainMenuState extends MusicBeatState FlxG.switchState(new TitleState()); #if MOD_SUPPORT - // make it customisable if (controls.SWITCHMOD) { openSubState(new ModSwitchMenu()); persistentUpdate = false; diff --git a/source/funkin/menus/ModSwitchMenu.hx b/source/funkin/menus/ModSwitchMenu.hx index 51509ebc..1b23b9d2 100644 --- a/source/funkin/menus/ModSwitchMenu.hx +++ b/source/funkin/menus/ModSwitchMenu.hx @@ -22,19 +22,7 @@ class ModSwitchMenu extends MusicBeatSubstate { bg.alpha = 0; FlxTween.tween(bg, {alpha: 0.5}, 0.25, {ease: FlxEase.cubeOut}); - for(modFolder in FileSystem.readDirectory(ModsFolder.modsPath)) { - if (FileSystem.isDirectory('${ModsFolder.modsPath}${modFolder}')) { - mods.push(modFolder); - } else { - var ext = Path.extension(modFolder).toLowerCase(); - switch(ext) { - case 'zip': - // is a zip mod!! - mods.push(Path.withoutExtension(modFolder)); - } - } - } - + mods = ModsFolder.getModsList(); mods.push(null); alphabets = new FlxTypedGroup(); diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index b0c34bf9..edc0cb61 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -161,7 +161,7 @@ class PauseSubState extends MusicBeatSubstate else { PlayState.resetSongInfos(); if (Charter.instance != null) Charter.instance.__clearStatics(); - + CoolUtil.playMenuSong(); FlxG.switchState(PlayState.isStoryMode ? new StoryMenuState() : new FreeplayState()); } diff --git a/source/funkin/menus/PlaytestingWarningSubstate.hx b/source/funkin/menus/PlaytestingWarningSubstate.hx index 047d42ce..e366bf9f 100644 --- a/source/funkin/menus/PlaytestingWarningSubstate.hx +++ b/source/funkin/menus/PlaytestingWarningSubstate.hx @@ -80,7 +80,7 @@ class PlaytestingWarningSubstate extends MusicBeatSubstate if (controls.RIGHT_P) changeSelection(1); if (controls.LEFT_P) changeSelection(-1); - + for (i => option in options) { option.x = FlxG.width * ((1+i)/4) - (option.fieldWidth/2); switch(i) { diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx index 0f8dd1d5..384118e5 100644 --- a/source/funkin/menus/StoryMenuState.hx +++ b/source/funkin/menus/StoryMenuState.hx @@ -116,7 +116,7 @@ class StoryMenuState extends MusicBeatState { changeWeek(0, true); - DiscordUtil.changePresence("In the Menus", null); + DiscordUtil.call("onMenuLoaded", ["Story Menu"]); CoolUtil.playMenuSong(); } @@ -276,7 +276,6 @@ class StoryMenuState extends MusicBeatState { } public function addCharacter(charName:String) { - var char:Access = null; try { char = new Access(Xml.parse(Assets.getText(Paths.xml('weeks/characters/$charName'))).firstElement()); @@ -307,7 +306,7 @@ class StoryMenuState extends MusicBeatState { } else { weeksFound = [for(c in Paths.getFolderContent('data/weeks/weeks/', false, source)) if (Path.extension(c).toLowerCase() == "xml") Path.withoutExtension(c)]; } - + if (weeksFound.length > 0) { for(s in weeksFound) weeks.push(s); diff --git a/source/funkin/menus/TitleState.hx b/source/funkin/menus/TitleState.hx index a24a8e86..4aca30db 100644 --- a/source/funkin/menus/TitleState.hx +++ b/source/funkin/menus/TitleState.hx @@ -34,8 +34,10 @@ class TitleState extends MusicBeatState MusicBeatState.skipTransIn = true; startIntro(); - + super.create(); + + DiscordUtil.call("onMenuLoaded", ["Title Screen"]); } var logoBl:FlxSprite; diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx index 2a9dd42b..579360ed 100644 --- a/source/funkin/menus/credits/CreditsCodename.hx +++ b/source/funkin/menus/credits/CreditsCodename.hx @@ -1,5 +1,7 @@ package funkin.menus.credits; +import funkin.options.PlayerSettings; +import flixel.util.FlxColor; import funkin.backend.system.github.GitHub; import funkin.options.type.GithubIconOption; import flixel.text.FlxText; @@ -9,44 +11,74 @@ using StringTools; class CreditsCodename extends funkin.options.OptionsScreen { public var error:Bool = false; public var author:String = "FNF-CNE-Devs"; + public var mainDevCol:FlxColor = 0xFF9C35D5; + public var minContrCol:FlxColor = 0xFFB4A7DA; + public var totalContributions:Int = 0; public override function new() { super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes)."); - checkUpdate(); - displayList(); + tryUpdating(true); } + // blame the secondary threads if the code has to look this bad - Nex + private var _canReset:Bool = true; + private var _downloadingSteps:Int = 0; public override function update(elapsed:Float) { - if (funkin.options.PlayerSettings.solo.controls.RESET && checkUpdate()) { + super.update(elapsed); + + if (_downloadingSteps == 2) { + _downloadingSteps = 0; + _canReset = true; displayList(); + } else if (_downloadingSteps == 1) { + _downloadingSteps = 0; + _canReset = true; updateMenuDesc(); } - super.update(elapsed); + else if(_canReset && PlayerSettings.solo.controls.RESET) tryUpdating(); + } + + public function tryUpdating(forceDisplaying:Bool = false) { + updateMenuDesc("Downloading List..."); + _canReset = false; + Main.execAsync(function() { + if(checkUpdate() || forceDisplaying) _downloadingSteps = 2; + else _downloadingSteps = 1; + }); } public override function updateMenuDesc(?txt:String) { + if(!_canReset) return; super.updateMenuDesc(txt); - parent.treeParent.pathDesc.applyMarkup(parent.treeParent.pathDesc.text, [new FlxTextFormatMarkerPair(new FlxTextFormat(0xFF9C35D5), "*")]); + updateMarkup(); + } + + public function updateMarkup() { + if(parent == null || parent.treeParent == null) return; + var text:String = parent.treeParent.pathDesc.text; + parent.treeParent.pathDesc.text = ""; + parent.treeParent.pathDesc.applyMarkup(text, [ + new FlxTextFormatMarkerPair(new FlxTextFormat(mainDevCol), '*'), + new FlxTextFormatMarkerPair(new FlxTextFormat(FlxColor.interpolate(minContrCol, mainDevCol, Options.contributors[curSelected].contributions / totalContributions)), '~') + ]); } public function checkUpdate():Bool { var curTime:Float = Date.now().getTime(); - if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits + if(Options.lastUpdated != null && curTime < Options.lastUpdated + 120000) return false; // Fuck you Github rate limits - Nex Options.lastUpdated = curTime; error = false; - //Main.execAsync(function() { var idk = GitHub.getContributors(author, "CodenameEngine", function(e) { error = true; var errMsg:String = 'Error while trying to download contributors list:\n${CoolUtil.removeIP(e.message)}'; Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR); funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); }); - //}); if(error) return false; Options.contributors = idk; - trace('List Updated!'); + trace('Contributors list Updated!'); var errorOnMain:Bool = false; var idk2 = GitHub.getOrganizationMembers(author, function(e) { @@ -55,7 +87,10 @@ class CreditsCodename extends funkin.options.OptionsScreen { Logs.traceColored([Logs.logText(errMsg.replace('\n', ' '), RED)], ERROR); funkin.backend.utils.NativeAPI.showMessageBox("Codename Engine Warning", errMsg, MSG_WARNING); }); - if(!errorOnMain) Options.mainDevs = [for(m in idk2) m.id]; + if(!errorOnMain) { + Options.mainDevs = [for(m in idk2) m.id]; + trace('Main Devs list Updated!'); + } return true; } @@ -70,13 +105,17 @@ class CreditsCodename extends funkin.options.OptionsScreen { remove(members[0], true); } - var totalContributions = 0; + totalContributions = 0; for(c in Options.contributors) totalContributions += c.contributions; for(c in Options.contributors) { - var desc:String = 'Total Contributions: ${c.contributions} / ${totalContributions} (${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%) - Select to open GitHub account'; - if(Options.mainDevs.contains(c.id)) desc += " *- Public member of the main Devs!*"; - var opt:GithubIconOption = new GithubIconOption(c, desc); + var opt:GithubIconOption = new GithubIconOption(c, 'Total Contributions: ~${c.contributions}~ / *${totalContributions}* (~${FlxMath.roundDecimal(c.contributions / totalContributions * 100, 2)}%~) - Select to open GitHub account'); + if(Options.mainDevs.contains(c.id)) { + opt.desc += " *- Public member of the main Devs!*"; + @:privateAccess opt.__text.color = mainDevCol; + } add(opt); } + + updateMenuDesc(); } } \ No newline at end of file diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx index 5dd16aff..56507274 100644 --- a/source/funkin/menus/credits/CreditsMain.hx +++ b/source/funkin/menus/credits/CreditsMain.hx @@ -43,6 +43,8 @@ class CreditsMain extends TreeMenu { main = new OptionsScreen('Credits', 'The people who made this possible!', selectables); super.create(); + + DiscordUtil.call("onMenuLoaded", ["Credits Menu"]); } /** diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index 3f27a109..a9bf72ac 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -43,7 +43,7 @@ class Options */ public static var intensiveBlur:Bool = true; public static var editorSFX:Bool = true; - public static var resizableEditors:Bool = true; + public static var editorPrettyPrint:Bool = false; public static var maxUndos:Int = 120; /** @@ -62,6 +62,11 @@ class Options public static var charterShowSections:Bool = true; public static var charterShowBeats:Bool = true; public static var charterEnablePlaytestScripts:Bool = true; + public static var charterLowDetailWaveforms:Bool = false; + public static var charterAutoSaves:Bool = true; + public static var charterAutoSaveTime:Float = 60*5; + public static var charterAutoSaveWarningTime:Float = 5; + public static var charterAutoSavesSeperateFolder:Bool = false; /** * PLAYER 1 CONTROLS diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx index e8226a9b..f9232e13 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -37,6 +37,8 @@ class OptionsMenu extends TreeMenu { CoolUtil.playMenuSong(); + DiscordUtil.call("onMenuLoaded", ["Options Menu"]); + var bg:FlxSprite = new FlxSprite(-80).loadAnimatedGraphic(Paths.image('menus/menuBGBlue')); // bg.scrollFactor.set(); bg.scale.set(1.15, 1.15); @@ -65,7 +67,7 @@ class OptionsMenu extends TreeMenu { } } })]); - + var xmlPath = Paths.xml("config/options"); for(source in [funkin.backend.assets.AssetsLibraryList.AssetSource.SOURCE, funkin.backend.assets.AssetsLibraryList.AssetSource.MODS]) { if (Paths.assetsTree.existsSpecific(xmlPath, "TEXT", source)) { @@ -75,13 +77,13 @@ class OptionsMenu extends TreeMenu { } catch(e) { Logs.trace('Error while parsing options.xml: ${Std.string(e)}', ERROR); } - + if (access != null) for(o in parseOptionsFromXML(access)) main.add(o); } } - + } public override function exit() { @@ -123,7 +125,7 @@ class OptionsMenu extends TreeMenu { Logs.trace("A choice option requires an \"id\" for option saving.", WARNING); continue; } - + var optionOptions:Array = []; var optionDisplayOptions:Array = []; @@ -131,10 +133,10 @@ class OptionsMenu extends TreeMenu { optionOptions.push(choice.att.value); optionDisplayOptions.push(choice.att.name); } - + if(optionOptions.length > 0) options.push(new ArrayOption(name, desc, optionOptions, optionDisplayOptions, node.att.id, null, FlxG.save.data)); - + case "menu": options.push(new TextOption(name + " >", desc, function() { optionsTree.add(new OptionsScreen(name, desc, parseOptionsFromXML(node))); diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx index ad4f01b9..45580821 100644 --- a/source/funkin/options/OptionsScreen.hx +++ b/source/funkin/options/OptionsScreen.hx @@ -70,11 +70,8 @@ class OptionsScreen extends FlxTypedSpriteGroup { } public function updateMenuDesc(?customTxt:String) { - if (parent.treeParent == null) return; - - var text:String = members[curSelected].desc; - if (customTxt != null) text = customTxt; - parent.treeParent.updateDesc(text); + if (parent == null || parent.treeParent == null) return; + parent.treeParent.updateDesc(customTxt != null ? customTxt : members[curSelected].desc); } public dynamic function onClose(o:OptionsScreen) {} diff --git a/source/funkin/options/OptionsTree.hx b/source/funkin/options/OptionsTree.hx index ae4d599c..c457d52e 100644 --- a/source/funkin/options/OptionsTree.hx +++ b/source/funkin/options/OptionsTree.hx @@ -53,10 +53,7 @@ class OptionsTree extends FlxTypedGroup { } public function clearLastMenu() { - if (lastMenu != null) { - lastMenu.destroy(); - lastMenu = null; - } + lastMenu = FlxDestroyUtil.destroy(lastMenu); } public override function destroy() { diff --git a/source/funkin/options/keybinds/KeybindSetting.hx b/source/funkin/options/keybinds/KeybindSetting.hx index 3fa1a9fc..8201f013 100644 --- a/source/funkin/options/keybinds/KeybindSetting.hx +++ b/source/funkin/options/keybinds/KeybindSetting.hx @@ -52,10 +52,10 @@ class KeybindSetting extends FlxTypedSpriteGroup { var min = Math.min(icon.scale.x, icon.scale.y); icon.scale.set(min, min); add(icon); - + title.setPosition(100, 0); } - + setPosition(x, y); } diff --git a/source/funkin/options/keybinds/KeybindsOptions.hx b/source/funkin/options/keybinds/KeybindsOptions.hx index cb974c47..e4e7d26c 100644 --- a/source/funkin/options/keybinds/KeybindsOptions.hx +++ b/source/funkin/options/keybinds/KeybindsOptions.hx @@ -192,7 +192,7 @@ class KeybindsOptions extends MusicBeatSubstate { if (canSelect) { changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0)); - + if (controls.BACK) { MusicBeatState.skipTransIn = true; if (isSubState) diff --git a/source/funkin/options/type/GithubIconOption.hx b/source/funkin/options/type/GithubIconOption.hx index cbbef1ed..5c780165 100644 --- a/source/funkin/options/type/GithubIconOption.hx +++ b/source/funkin/options/type/GithubIconOption.hx @@ -19,10 +19,10 @@ class GithubIconOption extends TextOption return usePortrait = value; } - public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true) { + public function new(user:Dynamic, desc:String, ?callback:Void->Void, ?customName:String, size:Int = 96, usePortrait:Bool = true, waitUntilLoad:Float = 0.25) { super(customName == null ? user.login : customName, desc, callback == null ? function() CoolUtil.openURL(user.html_url) : callback); this.user = user; - this.icon = new GithubUserIcon(user, size); + this.icon = new GithubUserIcon(user, size, waitUntilLoad); this.usePortrait = usePortrait; add(icon); } @@ -30,22 +30,28 @@ class GithubIconOption extends TextOption class GithubUserIcon extends FlxSprite { - private var loading:Bool = false; + public var waitUntilLoad:Null; private var user:Dynamic; private var size:Int; - public override function new(user:Dynamic, size:Int = 96) { + public override function new(user:Dynamic, size:Int = 96, waitUntilLoad:Float = 0.25) { this.user = user; this.size = size; + this.waitUntilLoad = waitUntilLoad; super(); makeGraphic(size, size, FlxColor.TRANSPARENT); antialiasing = true; } + override function update(elapsed:Float) { + if(waitUntilLoad > 0) waitUntilLoad -= elapsed; + super.update(elapsed); + } + final mutex = new sys.thread.Mutex(); override function drawComplex(camera:FlxCamera):Void { // Making the image downlaod only if the player actually sees it on the screeeeen - Nex - if(!loading) { - loading = true; + if(waitUntilLoad <= 0) { + waitUntilLoad = null; Main.execAsync(function() { var key:String = 'GITHUB-USER:${user.login}'; var bmap:Dynamic = FlxG.bitmap.get(key); @@ -57,7 +63,7 @@ class GithubUserIcon extends FlxSprite var bytes = null; if(unfLink) { - try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}?size=$size') + try bytes = HttpUtil.requestBytes('${user.avatar_url}?size=$size') catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)} - (Retrying using the api..)', RED)], ERROR); if(bytes != null) { @@ -68,7 +74,7 @@ class GithubUserIcon extends FlxSprite if(planB) { if(unfLink) user = GitHub.getUser(user.login, function(e) Logs.traceColored([Logs.logText('Failed to download github user info for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR)); // Api part - Nex - try bytes = GitHub.__requestBytesOnGitHubServers('${user.avatar_url}&size=$size') + try bytes = HttpUtil.requestBytes('${user.avatar_url}&size=$size') catch(e) Logs.traceColored([Logs.logText('Failed to download github pfp for ${user.login}: ${CoolUtil.removeIP(e.message)}', RED)], ERROR); if(bytes != null) bmap = BitmapData.fromBytes(bytes); diff --git a/source/lime/_internal/backend/native/NativeWindow.hx b/source/lime/_internal/backend/native/NativeWindow.hx index 9d318ec9..397d94ac 100644 --- a/source/lime/_internal/backend/native/NativeWindow.hx +++ b/source/lime/_internal/backend/native/NativeWindow.hx @@ -107,7 +107,7 @@ class NativeWindow #if (!macro && lime_cffi) handle = NativeCFFI.lime_window_create(parent.application.__backend.handle, width, height, flags, title); - + #if (DARK_MODE_WINDOW && !macro) funkin.backend.utils.NativeAPI.setDarkMode(title, true); #end diff --git a/source/openfl/display/DisplayObjectRenderer.hx b/source/openfl/display/DisplayObjectRenderer.hx new file mode 100644 index 00000000..02677424 --- /dev/null +++ b/source/openfl/display/DisplayObjectRenderer.hx @@ -0,0 +1,864 @@ +package openfl.display; + +#if !flash +import openfl.display._internal.Context3DGraphics; +import openfl.display.Bitmap; +import openfl.display.DisplayObject; +import openfl.display.Tilemap; +import openfl.events.EventDispatcher; +import openfl.events.RenderEvent; +import openfl.geom.ColorTransform; +import openfl.geom.Matrix; +import openfl.geom.Point; +import openfl.geom.Rectangle; +import openfl.text.TextField; +#if lime +import lime._internal.graphics.ImageCanvasUtil; // TODO +import lime.graphics.cairo.Cairo; +import lime.graphics.RenderContext; +import lime.graphics.RenderContextType; +#end + +#if !openfl_debug +@:fileXml('tags="haxe,release"') +@:noDebug +#end +@:access(openfl.display._internal.Context3DGraphics) +@:access(lime.graphics.ImageBuffer) +@:access(openfl.display.Bitmap) +@:access(openfl.display.BitmapData) +@:access(openfl.display.DisplayObject) +@:access(openfl.display.Graphics) +@:access(openfl.display.Tilemap) +@:access(openfl.display3D.Context3D) +@:access(openfl.events.RenderEvent) +@:access(openfl.filters.BitmapFilter) +@:access(openfl.geom.ColorTransform) +@:access(openfl.geom.Rectangle) +@:access(openfl.geom.Transform) +@:access(openfl.text.TextField) +@:allow(openfl.display._internal) +@:allow(openfl.display) +@:allow(openfl.text) +class DisplayObjectRenderer extends EventDispatcher +{ + @:noCompletion private var __allowSmoothing:Bool; + @:noCompletion private var __blendMode:BlendMode; + @:noCompletion private var __cleared:Bool; + @SuppressWarnings("checkstyle:Dynamic") @:noCompletion private var __context:#if lime RenderContext #else Dynamic #end; + @:noCompletion private var __overrideBlendMode:BlendMode; + @:noCompletion private var __pixelRatio:Float; + @:noCompletion private var __roundPixels:Bool; + @:noCompletion private var __stage:Stage; + @:noCompletion private var __tempColorTransform:ColorTransform; + @:noCompletion private var __transparent:Bool; + @SuppressWarnings("checkstyle:Dynamic") @:noCompletion private var __type:#if lime RenderContextType #else Dynamic #end; + @:noCompletion private var __worldAlpha:Float; + @:noCompletion private var __worldColorTransform:ColorTransform; + @:noCompletion private var __worldTransform:Matrix; + + @:noCompletion private function new() + { + super(); + + __allowSmoothing = true; + __pixelRatio = 1; + __tempColorTransform = new ColorTransform(); + __worldAlpha = 1; + } + + @:noCompletion private function __clear():Void {} + + @:noCompletion private function __getAlpha(value:Float):Float + { + return value * __worldAlpha; + } + + @:noCompletion private function __getColorTransform(value:ColorTransform):ColorTransform + { + if (__worldColorTransform != null) + { + __tempColorTransform.__copyFrom(__worldColorTransform); + __tempColorTransform.__combine(value); + return __tempColorTransform; + } + else + { + return value; + } + } + + @:noCompletion private function __popMask():Void {} + + @:noCompletion private function __popMaskObject(object:DisplayObject, handleScrollRect:Bool = true):Void {} + + @:noCompletion private function __popMaskRect():Void {} + + @:noCompletion private function __pushMask(mask:DisplayObject):Void {} + + @:noCompletion private function __pushMaskObject(object:DisplayObject, handleScrollRect:Bool = true):Void {} + + @:noCompletion private function __pushMaskRect(rect:Rectangle, transform:Matrix):Void {} + + @:noCompletion private function __render(object:IBitmapDrawable):Void {} + + @:noCompletion private function __renderEvent(displayObject:DisplayObject):Void + { + var renderer = this; + #if lime + if (displayObject.__customRenderEvent != null && displayObject.__renderable) + { + displayObject.__customRenderEvent.allowSmoothing = renderer.__allowSmoothing; + displayObject.__customRenderEvent.objectMatrix.copyFrom(displayObject.__renderTransform); + displayObject.__customRenderEvent.objectColorTransform.__copyFrom(displayObject.__worldColorTransform); + displayObject.__customRenderEvent.renderer = renderer; + + switch (renderer.__type) + { + case OPENGL: + if (!renderer.__cleared) renderer.__clear(); + + var renderer:OpenGLRenderer = cast renderer; + renderer.setShader(displayObject.__worldShader); + renderer.__context3D.__flushGL(); + + displayObject.__customRenderEvent.type = RenderEvent.RENDER_OPENGL; + + case CAIRO: + displayObject.__customRenderEvent.type = RenderEvent.RENDER_CAIRO; + + case DOM: + if (displayObject.stage != null && displayObject.__worldVisible) + { + displayObject.__customRenderEvent.type = RenderEvent.RENDER_DOM; + } + else + { + displayObject.__customRenderEvent.type = RenderEvent.CLEAR_DOM; + } + + case CANVAS: + displayObject.__customRenderEvent.type = RenderEvent.RENDER_CANVAS; + + default: + return; + } + + renderer.__setBlendMode(displayObject.__worldBlendMode); + renderer.__pushMaskObject(displayObject); + + displayObject.dispatchEvent(displayObject.__customRenderEvent); + + renderer.__popMaskObject(displayObject); + + if (renderer.__type == OPENGL) + { + var renderer:OpenGLRenderer = cast renderer; + renderer.setViewport(); + } + } + #end + } + + @:noCompletion private function __resize(width:Int, height:Int):Void {} + + @:noCompletion private function __setBlendMode(value:BlendMode):Void {} + + @:noCompletion private function __shouldCacheHardware(displayObject:DisplayObject, value:Null):Null + { + if (displayObject == null) return null; + + switch (displayObject.__drawableType) + { + case SPRITE, STAGE: + if (value == true) return true; + value = __shouldCacheHardware_DisplayObject(displayObject, value); + if (value == true) return true; + + if (displayObject.__children != null) + { + for (child in displayObject.__children) + { + value = __shouldCacheHardware_DisplayObject(child, value); + if (value == true) return true; + } + } + + return value; + + case TEXT_FIELD: + return value == true ? true : false; + + case TILEMAP: + return true; + + default: + return __shouldCacheHardware_DisplayObject(displayObject, value); + } + } + + @:noCompletion private function __shouldCacheHardware_DisplayObject(displayObject:DisplayObject, value:Null):Null + { + if (value == true || displayObject.__filters != null) return true; + + if (value == false || (displayObject.__graphics != null && !Context3DGraphics.isCompatible(displayObject.__graphics))) + { + return false; + } + + return null; + } + + @:noCompletion private function __updateCacheBitmap(displayObject:DisplayObject, force:Bool):Bool + { + if (displayObject == null) return false; + var renderer = this; + + switch (displayObject.__drawableType) + { + case BITMAP: + var bitmap:Bitmap = cast displayObject; + // TODO: Handle filters without an intermediate draw + if (bitmap.__bitmapData == null + || (bitmap.__filters == null #if lime && renderer.__type == OPENGL #end && bitmap.__cacheBitmap == null)) return false; + force = (bitmap.__bitmapData.image != null && bitmap.__bitmapData.image.version != bitmap.__imageVersion); + + case TEXT_FIELD: + var textField:TextField = cast displayObject; + if (textField.__filters == null #if lime && renderer.__type == OPENGL #end && textField.__cacheBitmap == null + && !textField.__domRender) return false; + if (force) textField.__renderDirty = true; + force = force || textField.__dirty; + + case TILEMAP: + var tilemap:Tilemap = cast displayObject; + if (tilemap.__filters == null #if lime && renderer.__type == OPENGL #end && tilemap.__cacheBitmap == null) return false; + + default: + } + + #if lime + if (displayObject.__isCacheBitmapRender) return false; + #if openfl_disable_cacheasbitmap + return false; + #end + + var colorTransform = ColorTransform.__pool.get(); + colorTransform.__copyFrom(displayObject.__worldColorTransform); + if (renderer.__worldColorTransform != null) colorTransform.__combine(renderer.__worldColorTransform); + var updated = false; + + // TODO: Do not force cacheAsBitmap on OpenGL once Scale-9 is properly supported in Context3DShape + if (displayObject.cacheAsBitmap + || (renderer.__type != OPENGL && !colorTransform.__isDefault(true)) + || (renderer.__type == OPENGL && displayObject.scale9Grid != null)) + { + var rect = null; + + var needRender = (displayObject.__cacheBitmap == null + || (displayObject.__renderDirty && (force || (displayObject.__children != null && displayObject.__children.length > 0))) + || displayObject.opaqueBackground != displayObject.__cacheBitmapBackground); + var softwareDirty = needRender + || (displayObject.__graphics != null && displayObject.__graphics.__softwareDirty) + || !displayObject.__cacheBitmapColorTransform.__equals(colorTransform, true); + var hardwareDirty = needRender || (displayObject.__graphics != null && displayObject.__graphics.__hardwareDirty); + + var renderType = renderer.__type; + + if (softwareDirty || hardwareDirty) + { + #if !openfl_force_gl_cacheasbitmap + if (renderType == OPENGL) + { + if (#if !openfl_disable_gl_cacheasbitmap __shouldCacheHardware(displayObject, null) == false #else true #end) + { + #if (js && html5) + renderType = CANVAS; + #else + renderType = CAIRO; + #end + } + } + #end + + if (softwareDirty && (renderType == CANVAS || renderType == CAIRO)) needRender = true; + if (hardwareDirty && renderType == OPENGL) needRender = true; + } + + var updateTransform = (needRender || !displayObject.__cacheBitmap.__worldTransform.equals(displayObject.__worldTransform)); + var hasFilters = #if !openfl_disable_filters displayObject.__filters != null #else false #end; + + #if !openfl_enable_cacheasbitmap + if (renderer.__type == DOM && !hasFilters) + { + return false; + } + #end + + if (hasFilters && !needRender) + { + for (filter in displayObject.__filters) + { + if (filter.__renderDirty) + { + needRender = true; + break; + } + } + } + + if (displayObject.__cacheBitmapMatrix == null) + { + displayObject.__cacheBitmapMatrix = new Matrix(); + } + + var bitmapMatrix = (displayObject.__cacheAsBitmapMatrix != null ? displayObject.__cacheAsBitmapMatrix : displayObject.__renderTransform); + + if (!needRender + && (bitmapMatrix.a != displayObject.__cacheBitmapMatrix.a + || bitmapMatrix.b != displayObject.__cacheBitmapMatrix.b + || bitmapMatrix.c != displayObject.__cacheBitmapMatrix.c + || bitmapMatrix.d != displayObject.__cacheBitmapMatrix.d)) + { + needRender = true; + } + + if (!needRender + && renderer.__type != OPENGL + && displayObject.__cacheBitmapData != null + && displayObject.__cacheBitmapData.image != null + && displayObject.__cacheBitmapData.image.version < displayObject.__cacheBitmapData.__textureVersion) + { + needRender = true; + } + + // Ensure that cached bitmap is updated after changes to scrollRect + if (!needRender) + { + var current = displayObject; + while (current != null) + { + if (current.scrollRect != null) + { + // TODO: do we need to update transform if scroll rects haven't changed? + updateTransform = true; + break; + } + current = current.parent; + } + } + + displayObject.__cacheBitmapMatrix.copyFrom(bitmapMatrix); + displayObject.__cacheBitmapMatrix.tx = 0; + displayObject.__cacheBitmapMatrix.ty = 0; + + // TODO: Handle dimensions better if object has a scrollRect? + + var bitmapWidth = 0, bitmapHeight = 0; + var filterWidth = 0, filterHeight = 0; + var offsetX = 0., offsetY = 0.; + + #if (openfl_disable_hdpi || openfl_disable_hdpi_cacheasbitmap) + var pixelRatio = 1; + #else + var pixelRatio = __pixelRatio; + #end + + if (updateTransform || needRender) + { + rect = Rectangle.__pool.get(); + + displayObject.__getFilterBounds(rect, displayObject.__cacheBitmapMatrix); + + filterWidth = rect.width > 0 ? Math.floor(rect.width * pixelRatio) : 0; + filterHeight = rect.height > 0 ? Math.floor(rect.height * pixelRatio) : 0; + + offsetX = rect.x > 0 ? Math.ceil(rect.x) : Math.floor(rect.x); + offsetY = rect.y > 0 ? Math.ceil(rect.y) : Math.floor(rect.y); + + if (displayObject.__cacheBitmapData != null) + { + if (filterWidth > displayObject.__cacheBitmapData.width || filterHeight > displayObject.__cacheBitmapData.height) + { + bitmapWidth = Math.ceil(Math.max(filterWidth * 1.25, displayObject.__cacheBitmapData.width)); + bitmapHeight = Math.ceil(Math.max(filterHeight * 1.25, displayObject.__cacheBitmapData.height)); + needRender = true; + } + else + { + bitmapWidth = displayObject.__cacheBitmapData.width; + bitmapHeight = displayObject.__cacheBitmapData.height; + } + } + else + { + bitmapWidth = filterWidth; + bitmapHeight = filterHeight; + } + } + + if (needRender) + { + updateTransform = true; + displayObject.__cacheBitmapBackground = displayObject.opaqueBackground; + + if (filterWidth >= 0.5 && filterHeight >= 0.5) + { + var needsFill = (displayObject.opaqueBackground != null + && (bitmapWidth != filterWidth || bitmapHeight != filterHeight)); + var fillColor = displayObject.opaqueBackground != null ? (0xFF << 24) | displayObject.opaqueBackground : 0; + var bitmapColor = needsFill ? 0 : fillColor; + var allowFramebuffer = (renderer.__type == OPENGL); + + if (displayObject.__cacheBitmapData == null + || bitmapWidth > displayObject.__cacheBitmapData.width + || bitmapHeight > displayObject.__cacheBitmapData.height) + { + displayObject.__cacheBitmapData = new BitmapData(bitmapWidth, bitmapHeight, true, bitmapColor); + + if (displayObject.__cacheBitmap == null) displayObject.__cacheBitmap = new Bitmap(); + displayObject.__cacheBitmap.__bitmapData = displayObject.__cacheBitmapData; + displayObject.__cacheBitmapRenderer = null; + } + else + { + displayObject.__cacheBitmapData.__fillRect(displayObject.__cacheBitmapData.rect, bitmapColor, allowFramebuffer); + } + + if (needsFill) + { + rect.setTo(0, 0, filterWidth, filterHeight); + displayObject.__cacheBitmapData.__fillRect(rect, fillColor, allowFramebuffer); + } + } + else + { + ColorTransform.__pool.release(colorTransform); + + displayObject.__cacheBitmap = null; + displayObject.__cacheBitmapData = null; + displayObject.__cacheBitmapData2 = null; + displayObject.__cacheBitmapData3 = null; + displayObject.__cacheBitmapRenderer = null; + + if (displayObject.__drawableType == TEXT_FIELD) + { + var textField:TextField = cast displayObject; + if (textField.__cacheBitmap != null) + { + textField.__cacheBitmap.__renderTransform.tx -= textField.__offsetX * pixelRatio; + textField.__cacheBitmap.__renderTransform.ty -= textField.__offsetY * pixelRatio; + } + } + + return true; + } + } + else + { + // Should we retain these longer? + + displayObject.__cacheBitmapData = displayObject.__cacheBitmap.bitmapData; + displayObject.__cacheBitmapData2 = null; + displayObject.__cacheBitmapData3 = null; + } + + if (updateTransform || needRender) + { + displayObject.__cacheBitmap.__worldTransform.copyFrom(displayObject.__worldTransform); + + if (bitmapMatrix == displayObject.__renderTransform) + { + displayObject.__cacheBitmap.__renderTransform.identity(); + displayObject.__cacheBitmap.__renderTransform.scale(1 / pixelRatio, 1 / pixelRatio); + displayObject.__cacheBitmap.__renderTransform.tx = displayObject.__renderTransform.tx + offsetX; + displayObject.__cacheBitmap.__renderTransform.ty = displayObject.__renderTransform.ty + offsetY; + } + else + { + displayObject.__cacheBitmap.__renderTransform.copyFrom(displayObject.__cacheBitmapMatrix); + displayObject.__cacheBitmap.__renderTransform.invert(); + displayObject.__cacheBitmap.__renderTransform.concat(displayObject.__renderTransform); + displayObject.__cacheBitmap.__renderTransform.a *= 1 / pixelRatio; + displayObject.__cacheBitmap.__renderTransform.d *= 1 / pixelRatio; + displayObject.__cacheBitmap.__renderTransform.tx += offsetX; + displayObject.__cacheBitmap.__renderTransform.ty += offsetY; + } + } + + displayObject.__cacheBitmap.smoothing = renderer.__allowSmoothing; + displayObject.__cacheBitmap.__renderable = displayObject.__renderable; + displayObject.__cacheBitmap.__worldAlpha = displayObject.__worldAlpha; + displayObject.__cacheBitmap.__worldBlendMode = displayObject.__worldBlendMode; + displayObject.__cacheBitmap.__worldShader = displayObject.__worldShader; + // displayObject.__cacheBitmap.__scrollRect = displayObject.__scrollRect; + // displayObject.__cacheBitmap.filters = displayObject.filters; + displayObject.__cacheBitmap.mask = displayObject.__mask; + + if (needRender) + { + #if lime + if (displayObject.__cacheBitmapRenderer == null || renderType != displayObject.__cacheBitmapRenderer.__type) + { + if (renderType == OPENGL) + { + displayObject.__cacheBitmapRenderer = new OpenGLRenderer(cast(renderer, OpenGLRenderer).__context3D, displayObject.__cacheBitmapData); + } + else + { + if (displayObject.__cacheBitmapData.image == null) + { + var color = displayObject.opaqueBackground != null ? (0xFF << 24) | displayObject.opaqueBackground : 0; + displayObject.__cacheBitmapData = new BitmapData(bitmapWidth, bitmapHeight, true, color); + displayObject.__cacheBitmap.__bitmapData = displayObject.__cacheBitmapData; + } + + #if (js && html5) + ImageCanvasUtil.convertToCanvas(displayObject.__cacheBitmapData.image); + displayObject.__cacheBitmapRenderer = new CanvasRenderer(displayObject.__cacheBitmapData.image.buffer.__srcContext); + #else + displayObject.__cacheBitmapRenderer = new CairoRenderer(new Cairo(displayObject.__cacheBitmapData.getSurface())); + #end + } + + displayObject.__cacheBitmapRenderer.__worldTransform = new Matrix(); + displayObject.__cacheBitmapRenderer.__worldColorTransform = new ColorTransform(); + } + #else + return false; + #end + + if (displayObject.__cacheBitmapColorTransform == null) displayObject.__cacheBitmapColorTransform = new ColorTransform(); + + displayObject.__cacheBitmapRenderer.__stage = displayObject.stage; + + displayObject.__cacheBitmapRenderer.__allowSmoothing = renderer.__allowSmoothing; + displayObject.__cacheBitmapRenderer.__setBlendMode(NORMAL); + displayObject.__cacheBitmapRenderer.__worldAlpha = 1 / displayObject.__worldAlpha; + + displayObject.__cacheBitmapRenderer.__worldTransform.copyFrom(displayObject.__renderTransform); + displayObject.__cacheBitmapRenderer.__worldTransform.invert(); + displayObject.__cacheBitmapRenderer.__worldTransform.concat(displayObject.__cacheBitmapMatrix); + displayObject.__cacheBitmapRenderer.__worldTransform.tx -= offsetX; + displayObject.__cacheBitmapRenderer.__worldTransform.ty -= offsetY; + displayObject.__cacheBitmapRenderer.__worldTransform.scale(pixelRatio, pixelRatio); + + displayObject.__cacheBitmapRenderer.__pixelRatio = pixelRatio; + + displayObject.__cacheBitmapRenderer.__worldColorTransform.__copyFrom(colorTransform); + displayObject.__cacheBitmapRenderer.__worldColorTransform.__invert(); + + displayObject.__isCacheBitmapRender = true; + + if (displayObject.__cacheBitmapRenderer.__type == OPENGL) + { + var parentRenderer:OpenGLRenderer = cast renderer; + var childRenderer:OpenGLRenderer = cast displayObject.__cacheBitmapRenderer; + + var context = childRenderer.__context3D; + + var cacheRTT = context.__state.renderToTexture; + var cacheRTTDepthStencil = context.__state.renderToTextureDepthStencil; + var cacheRTTAntiAlias = context.__state.renderToTextureAntiAlias; + var cacheRTTSurfaceSelector = context.__state.renderToTextureSurfaceSelector; + + // var cacheFramebuffer = context.__contextState.__currentGLFramebuffer; + + var cacheBlendMode = parentRenderer.__blendMode; + parentRenderer.__suspendClipAndMask(); + childRenderer.__copyShader(parentRenderer); + // childRenderer.__copyState (parentRenderer); + + displayObject.__cacheBitmapData.__setUVRect(context, 0, 0, filterWidth, filterHeight); + childRenderer.__setRenderTarget(displayObject.__cacheBitmapData); + if (displayObject.__cacheBitmapData.image != null) + displayObject.__cacheBitmapData.__textureVersion = displayObject.__cacheBitmapData.image.version + + 1; + + displayObject.__cacheBitmapData.__drawGL(displayObject, childRenderer); + + if (hasFilters) + { + var needSecondBitmapData = true; + var needCopyOfOriginal = false; + + for (filter in displayObject.__filters) + { + // if (filter.__needSecondBitmapData) { + // needSecondBitmapData = true; + // } + if (filter.__preserveObject) + { + needCopyOfOriginal = true; + } + } + + var bitmap = displayObject.__cacheBitmapData; + var bitmap2 = null; + var bitmap3 = null; + + // if (needSecondBitmapData) { + if (displayObject.__cacheBitmapData2 == null + || bitmapWidth > displayObject.__cacheBitmapData2.width + || bitmapHeight > displayObject.__cacheBitmapData2.height) + { + displayObject.__cacheBitmapData2 = new BitmapData(bitmapWidth, bitmapHeight, true, 0); + } + else + { + displayObject.__cacheBitmapData2.fillRect(displayObject.__cacheBitmapData2.rect, 0); + if (displayObject.__cacheBitmapData2.image != null) + { + displayObject.__cacheBitmapData2.__textureVersion = displayObject.__cacheBitmapData2.image.version + 1; + } + } + displayObject.__cacheBitmapData2.__setUVRect(context, 0, 0, filterWidth, filterHeight); + bitmap2 = displayObject.__cacheBitmapData2; + // } else { + // bitmap2 = bitmapData; + // } + + if (needCopyOfOriginal) + { + if (displayObject.__cacheBitmapData3 == null + || bitmapWidth > displayObject.__cacheBitmapData3.width + || bitmapHeight > displayObject.__cacheBitmapData3.height) + { + displayObject.__cacheBitmapData3 = new BitmapData(bitmapWidth, bitmapHeight, true, 0); + } + else + { + displayObject.__cacheBitmapData3.fillRect(displayObject.__cacheBitmapData3.rect, 0); + if (displayObject.__cacheBitmapData3.image != null) + { + displayObject.__cacheBitmapData3.__textureVersion = displayObject.__cacheBitmapData3.image.version + 1; + } + } + displayObject.__cacheBitmapData3.__setUVRect(context, 0, 0, filterWidth, filterHeight); + bitmap3 = displayObject.__cacheBitmapData3; + } + + childRenderer.__setBlendMode(NORMAL); + childRenderer.__worldAlpha = 1; + childRenderer.__worldTransform.identity(); + childRenderer.__worldColorTransform.__identity(); + + // var sourceRect = bitmap.rect; + // if (__tempPoint == null) __tempPoint = new Point (); + // var destPoint = __tempPoint; + var shader, cacheBitmap; + + for (filter in displayObject.__filters) + { + if (filter.__preserveObject) + { + childRenderer.__setRenderTarget(bitmap3); + childRenderer.__renderFilterPass(bitmap, childRenderer.__defaultDisplayShader, filter.__smooth); + } + + for (i in 0...filter.__numShaderPasses) + { + shader = filter.__initShader(childRenderer, i, filter.__preserveObject ? bitmap3 : null); + childRenderer.__setBlendMode(filter.__shaderBlendMode); + childRenderer.__setRenderTarget(bitmap2); + childRenderer.__renderFilterPass(bitmap, shader, filter.__smooth); + + cacheBitmap = bitmap; + bitmap = bitmap2; + bitmap2 = cacheBitmap; + } + + filter.__renderDirty = false; + } + + displayObject.__cacheBitmap.__bitmapData = bitmap; + } + + parentRenderer.__blendMode = NORMAL; + parentRenderer.__setBlendMode(cacheBlendMode); + parentRenderer.__copyShader(childRenderer); + + if (cacheRTT != null) + { + context.setRenderToTexture(cacheRTT, cacheRTTDepthStencil, cacheRTTAntiAlias, cacheRTTSurfaceSelector); + } + else + { + context.setRenderToBackBuffer(); + } + + // context.__bindGLFramebuffer (cacheFramebuffer); + + // parentRenderer.__restoreState (childRenderer); + parentRenderer.__resumeClipAndMask(childRenderer); + parentRenderer.setViewport(); + + displayObject.__cacheBitmapColorTransform.__copyFrom(colorTransform); + } + else + { + #if (js && html5) + displayObject.__cacheBitmapData.__drawCanvas(displayObject, cast displayObject.__cacheBitmapRenderer); + #else + displayObject.__cacheBitmapData.__drawCairo(displayObject, cast displayObject.__cacheBitmapRenderer); + #end + + if (hasFilters) + { + var needSecondBitmapData = false; + var needCopyOfOriginal = false; + + for (filter in displayObject.__filters) + { + if (filter.__needSecondBitmapData) + { + needSecondBitmapData = true; + } + if (filter.__preserveObject) + { + needCopyOfOriginal = true; + } + } + + var bitmap = displayObject.__cacheBitmapData; + var bitmap2 = null; + var bitmap3 = null; + + if (needSecondBitmapData) + { + if (displayObject.__cacheBitmapData2 == null + || displayObject.__cacheBitmapData2.image == null + || bitmapWidth > displayObject.__cacheBitmapData2.width + || bitmapHeight > displayObject.__cacheBitmapData2.height) + { + displayObject.__cacheBitmapData2 = new BitmapData(bitmapWidth, bitmapHeight, true, 0); + } + else + { + displayObject.__cacheBitmapData2.fillRect(displayObject.__cacheBitmapData2.rect, 0); + } + bitmap2 = displayObject.__cacheBitmapData2; + } + else + { + bitmap2 = bitmap; + } + + if (needCopyOfOriginal) + { + if (displayObject.__cacheBitmapData3 == null + || displayObject.__cacheBitmapData3.image == null + || bitmapWidth > displayObject.__cacheBitmapData3.width + || bitmapHeight > displayObject.__cacheBitmapData3.height) + { + displayObject.__cacheBitmapData3 = new BitmapData(bitmapWidth, bitmapHeight, true, 0); + } + else + { + displayObject.__cacheBitmapData3.fillRect(displayObject.__cacheBitmapData3.rect, 0); + } + bitmap3 = displayObject.__cacheBitmapData3; + } + + if (displayObject.__tempPoint == null) displayObject.__tempPoint = new Point(); + var destPoint = displayObject.__tempPoint; + var cacheBitmap, lastBitmap; + + for (filter in displayObject.__filters) + { + if (filter.__preserveObject) + { + bitmap3.copyPixels(bitmap, bitmap.rect, destPoint); + } + + lastBitmap = filter.__applyFilter(bitmap2, bitmap, bitmap.rect, destPoint); + + if (filter.__preserveObject) + { + lastBitmap.draw(bitmap3, null, + displayObject.__objectTransform != null ? displayObject.__objectTransform.__colorTransform : null); + } + filter.__renderDirty = false; + + if (needSecondBitmapData && lastBitmap == bitmap2) + { + cacheBitmap = bitmap; + bitmap = bitmap2; + bitmap2 = cacheBitmap; + } + } + + if (displayObject.__cacheBitmapData != bitmap) + { + // TODO: Fix issue with swapping __cacheBitmap.__bitmapData + // __cacheBitmapData.copyPixels (bitmap, bitmap.rect, destPoint); + + // Adding __cacheBitmapRenderer = null; makes this work + cacheBitmap = displayObject.__cacheBitmapData; + displayObject.__cacheBitmapData = bitmap; + displayObject.__cacheBitmapData2 = cacheBitmap; + displayObject.__cacheBitmap.__bitmapData = displayObject.__cacheBitmapData; + displayObject.__cacheBitmapRenderer = null; + } + + displayObject.__cacheBitmap.__imageVersion = displayObject.__cacheBitmapData.__textureVersion; + } + + displayObject.__cacheBitmapColorTransform.__copyFrom(colorTransform); + + if (!displayObject.__cacheBitmapColorTransform.__isDefault(true)) + { + displayObject.__cacheBitmapColorTransform.alphaMultiplier = 1; + displayObject.__cacheBitmapData.colorTransform(displayObject.__cacheBitmapData.rect, displayObject.__cacheBitmapColorTransform); + } + } + + displayObject.__isCacheBitmapRender = false; + } + + if (updateTransform || needRender) + { + Rectangle.__pool.release(rect); + } + + updated = updateTransform; + } + else if (displayObject.__cacheBitmap != null) + { + if (renderer.__type == DOM) + { + var domRenderer:DOMRenderer = cast renderer; + domRenderer.__renderDrawableClear(displayObject.__cacheBitmap); + } + + displayObject.__cacheBitmap = null; + displayObject.__cacheBitmapData = null; + displayObject.__cacheBitmapData2 = null; + displayObject.__cacheBitmapData3 = null; + displayObject.__cacheBitmapColorTransform = null; + displayObject.__cacheBitmapRenderer = null; + + updated = true; + } + + ColorTransform.__pool.release(colorTransform); + + if (updated && displayObject.__drawableType == TEXT_FIELD) + { + var textField:TextField = cast displayObject; + if (textField.__cacheBitmap != null) + { + textField.__cacheBitmap.__renderTransform.tx -= textField.__offsetX; + textField.__cacheBitmap.__renderTransform.ty -= textField.__offsetY; + } + } + + return updated; + #else + return false; + #end + } +} +#else +typedef DisplayObjectRenderer = Dynamic; +#end