diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index dd818bf4a..2ba3b31ed 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -14,6 +14,8 @@ labels: bug [ ] Windows [ ] Linux [ ] Mac +[ ] Android +[ ] iOS ### Explain your issue ##### Please check first if your issue haven't already been reported yet. diff --git a/.github/ISSUE_TEMPLATE/compiling.md b/.github/ISSUE_TEMPLATE/compiling.md index 4f516469e..cc8d321a2 100644 --- a/.github/ISSUE_TEMPLATE/compiling.md +++ b/.github/ISSUE_TEMPLATE/compiling.md @@ -14,6 +14,8 @@ labels: compiling help [ ] Windows [ ] Linux [ ] Mac +[ ] Android +[ ] iOS ### Explain your issue ##### Please check first if your issue haven't already been reported yet, and make sure you ran the `update.bat` file before building. diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..4d2a21045 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,75 @@ +name: Android Build +on: + push: + workflow_dispatch: +jobs: + build: + name: Android Build + permissions: write-all + runs-on: ubuntu-latest + steps: + - name: Pulling the new commit + uses: actions/checkout@v2 + - name: Setup Haxe + uses: krdlab/setup-haxe@v1 + with: + haxe-version: 4.2.5 + - name: Restore existing build cache for faster compilation + uses: actions/cache@v3 + with: + # not caching the bin folder to prevent asset duplication and stuff like that + key: cache-build-android + path: | + .haxelib/ + export/release/android/haxe/ + export/release/android/obj/ + restore-keys: | + cache-build-android + - name: Installing/Updating libraries + run: | + haxe -cp commandline -D analyzer-optimize --run Main setup -s --lib=./libs.mobile.xml + - name: Configuring Android + run: | + haxelib run lime config ANDROID_SDK $ANDROID_HOME + haxelib run lime config ANDROID_NDK_ROOT $ANDROID_NDK_LATEST_HOME + haxelib run lime config JAVA_HOME $JAVA_HOME_17_X64 + haxelib run lime config ANDROID_SETUP true + - name: Building the game + run: haxelib run lime build android + - name: Uploading artifact + uses: actions/upload-artifact@v4 + with: + name: Codename Engine + path: export/release/android/bin/app/build/outputs/apk/release/*.apk + - name: Remove Docker Images + run: docker rmi $(docker image ls -aq) + - name: Clearing already existing cache + uses: actions/github-script@v6 + with: + script: | + const caches = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + for (const cache of caches.data.actions_caches) { + if (cache.key == "cache-build-android") { + console.log('Clearing ' + cache.key + '...') + await github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }) + console.log("Cache cleared.") + } + } + - name: Uploading new cache + uses: actions/cache@v3 + with: + # caching again since for some reason it doesnt work with the first post cache shit + key: cache-build-android + path: | + .haxelib/ + export/release/android/haxe/ + export/release/android/obj/ + restore-keys: | + cache-build-android diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 000000000..a8a984f69 --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,71 @@ +name: iOS Build +on: + push: + workflow_dispatch: +jobs: + build: + name: iOS Build + permissions: write-all + runs-on: macos-13 + steps: + - name: Pulling the new commit + uses: actions/checkout@v2 + - name: Setting up Haxe + uses: krdlab/setup-haxe@v1 + with: + haxe-version: 4.2.5 + - name: Restore existing build cache for faster compilation + uses: actions/cache@v3 + with: + # not caching the bin folder to prevent asset duplication and stuff like that + key: cache-build-ios + path: | + .haxelib/ + export/release/ios/CodenameEngine/haxe/ + restore-keys: | + cache-build-ios + - name: Installing/Updating libraries + run: | + haxe -cp commandline -D analyzer-optimize --run Main setup -s --lib=./libs.mobile.xml + - name: Building the game + run: haxelib run lime build ios -nosign + - name: Clearing already existing cache + uses: actions/github-script@v6 + with: + script: | + const caches = await github.rest.actions.getActionsCacheList({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + for (const cache of caches.data.actions_caches) { + if (cache.key == "cache-build-ios") { + console.log('Clearing ' + cache.key + '...') + await github.rest.actions.deleteActionsCacheById({ + owner: context.repo.owner, + repo: context.repo.repo, + cache_id: cache.id, + }) + console.log("Cache cleared.") + } + } + - name: Making ipa file + run: | + cd export/*/ios/build/*-iphoneos + mkdir Payload + mv *.app Payload + zip -r CodenameEngine.ipa Payload + - name: Uploading artifact + uses: actions/upload-artifact@v4 + with: + name: Codename Engine + path: export/release/ios/build/Release-iphoneos/*.ipa + - name: Uploading new cache + uses: actions/cache@v3 + with: + # caching again since for some reason it doesnt work with the first post cache shit + key: cache-build-ios + path: | + .haxelib/ + export/release/ios/CodenameEngine/haxe + restore-keys: | + cache-build-ios diff --git a/README.md b/README.md index 2d95c17b2..8bb1f46fb 100644 --- a/README.md +++ b/README.md @@ -87,3 +87,11 @@ In the future (when the engine won't be a WIP anymore) we're gonna also publish - Credits to Smokey555 for the backup Animate Atlas to spritesheet code - 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) + +
+

Mobile Credits

+ +- Credits to [Lily](ttps://youtube.com/@mcagabe19) to porting the engine +- Credits to [Karim Akra](https://youtube.com/@Karim0690) to helping me to port the engine +- Credits to [MAJigsaw77](https://github.com/MAJigsaw77) for mobile controls +
diff --git a/assets/data/scripts/week6-pause.hx b/assets/data/scripts/week6-pause.hx index a80598ce1..bde239147 100644 --- a/assets/data/scripts/week6-pause.hx +++ b/assets/data/scripts/week6-pause.hx @@ -62,6 +62,9 @@ function create(event) { cameras = [pauseCam]; FlxG.sound.play(Paths.sound(isThorns ? 'pixel/ANGRY' : 'pixel/clickText')); + + addVirtualPad('UP_DOWN', 'A'); + addVirtualPadCamera(); } function confText(text) { diff --git a/assets/images/menus/funkay.png b/assets/images/menus/funkay.png new file mode 100644 index 000000000..1d6b731bd Binary files /dev/null and b/assets/images/menus/funkay.png differ diff --git a/assets/images/mobile/menu/arrows.png b/assets/images/mobile/menu/arrows.png new file mode 100644 index 000000000..96158666b Binary files /dev/null and b/assets/images/mobile/menu/arrows.png differ diff --git a/assets/images/mobile/menu/arrows.xml b/assets/images/mobile/menu/arrows.xml new file mode 100644 index 000000000..df670d955 --- /dev/null +++ b/assets/images/mobile/menu/arrows.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/mobile/virtualpad/a.png b/assets/images/mobile/virtualpad/a.png new file mode 100644 index 000000000..7e0abf3be Binary files /dev/null and b/assets/images/mobile/virtualpad/a.png differ diff --git a/assets/images/mobile/virtualpad/b.png b/assets/images/mobile/virtualpad/b.png new file mode 100644 index 000000000..93cf690e6 Binary files /dev/null and b/assets/images/mobile/virtualpad/b.png differ diff --git a/assets/images/mobile/virtualpad/c.png b/assets/images/mobile/virtualpad/c.png new file mode 100644 index 000000000..ea5eb9900 Binary files /dev/null and b/assets/images/mobile/virtualpad/c.png differ diff --git a/assets/images/mobile/virtualpad/d.png b/assets/images/mobile/virtualpad/d.png new file mode 100644 index 000000000..75fedc606 Binary files /dev/null and b/assets/images/mobile/virtualpad/d.png differ diff --git a/assets/images/mobile/virtualpad/default.png b/assets/images/mobile/virtualpad/default.png new file mode 100644 index 000000000..5d3a11dcb Binary files /dev/null and b/assets/images/mobile/virtualpad/default.png differ diff --git a/assets/images/mobile/virtualpad/down.png b/assets/images/mobile/virtualpad/down.png new file mode 100644 index 000000000..cc802e53f Binary files /dev/null and b/assets/images/mobile/virtualpad/down.png differ diff --git a/assets/images/mobile/virtualpad/e.png b/assets/images/mobile/virtualpad/e.png new file mode 100644 index 000000000..004693125 Binary files /dev/null and b/assets/images/mobile/virtualpad/e.png differ diff --git a/assets/images/mobile/virtualpad/f.png b/assets/images/mobile/virtualpad/f.png new file mode 100644 index 000000000..28df192d6 Binary files /dev/null and b/assets/images/mobile/virtualpad/f.png differ diff --git a/assets/images/mobile/virtualpad/g.png b/assets/images/mobile/virtualpad/g.png new file mode 100644 index 000000000..e3835860b Binary files /dev/null and b/assets/images/mobile/virtualpad/g.png differ diff --git a/assets/images/mobile/virtualpad/h.png b/assets/images/mobile/virtualpad/h.png new file mode 100644 index 000000000..90260779a Binary files /dev/null and b/assets/images/mobile/virtualpad/h.png differ diff --git a/assets/images/mobile/virtualpad/i.png b/assets/images/mobile/virtualpad/i.png new file mode 100644 index 000000000..3c1787f03 Binary files /dev/null and b/assets/images/mobile/virtualpad/i.png differ diff --git a/assets/images/mobile/virtualpad/j.png b/assets/images/mobile/virtualpad/j.png new file mode 100644 index 000000000..5e4086c1c Binary files /dev/null and b/assets/images/mobile/virtualpad/j.png differ diff --git a/assets/images/mobile/virtualpad/k.png b/assets/images/mobile/virtualpad/k.png new file mode 100644 index 000000000..8576c8d69 Binary files /dev/null and b/assets/images/mobile/virtualpad/k.png differ diff --git a/assets/images/mobile/virtualpad/l.png b/assets/images/mobile/virtualpad/l.png new file mode 100644 index 000000000..56209bfca Binary files /dev/null and b/assets/images/mobile/virtualpad/l.png differ diff --git a/assets/images/mobile/virtualpad/left.png b/assets/images/mobile/virtualpad/left.png new file mode 100644 index 000000000..6f3a11b1c Binary files /dev/null and b/assets/images/mobile/virtualpad/left.png differ diff --git a/assets/images/mobile/virtualpad/m.png b/assets/images/mobile/virtualpad/m.png new file mode 100644 index 000000000..a73db43d4 Binary files /dev/null and b/assets/images/mobile/virtualpad/m.png differ diff --git a/assets/images/mobile/virtualpad/n.png b/assets/images/mobile/virtualpad/n.png new file mode 100644 index 000000000..4844e113a Binary files /dev/null and b/assets/images/mobile/virtualpad/n.png differ diff --git a/assets/images/mobile/virtualpad/o.png b/assets/images/mobile/virtualpad/o.png new file mode 100644 index 000000000..d53a7d63b Binary files /dev/null and b/assets/images/mobile/virtualpad/o.png differ diff --git a/assets/images/mobile/virtualpad/p.png b/assets/images/mobile/virtualpad/p.png new file mode 100644 index 000000000..eabcfe131 Binary files /dev/null and b/assets/images/mobile/virtualpad/p.png differ diff --git a/assets/images/mobile/virtualpad/q.png b/assets/images/mobile/virtualpad/q.png new file mode 100644 index 000000000..cb6ae7743 Binary files /dev/null and b/assets/images/mobile/virtualpad/q.png differ diff --git a/assets/images/mobile/virtualpad/r.png b/assets/images/mobile/virtualpad/r.png new file mode 100644 index 000000000..e21377370 Binary files /dev/null and b/assets/images/mobile/virtualpad/r.png differ diff --git a/assets/images/mobile/virtualpad/right.png b/assets/images/mobile/virtualpad/right.png new file mode 100644 index 000000000..95a8a4ff8 Binary files /dev/null and b/assets/images/mobile/virtualpad/right.png differ diff --git a/assets/images/mobile/virtualpad/s.png b/assets/images/mobile/virtualpad/s.png new file mode 100644 index 000000000..02d386f99 Binary files /dev/null and b/assets/images/mobile/virtualpad/s.png differ diff --git a/assets/images/mobile/virtualpad/t.png b/assets/images/mobile/virtualpad/t.png new file mode 100644 index 000000000..af9f5103c Binary files /dev/null and b/assets/images/mobile/virtualpad/t.png differ diff --git a/assets/images/mobile/virtualpad/u.png b/assets/images/mobile/virtualpad/u.png new file mode 100644 index 000000000..273f3723a Binary files /dev/null and b/assets/images/mobile/virtualpad/u.png differ diff --git a/assets/images/mobile/virtualpad/up.png b/assets/images/mobile/virtualpad/up.png new file mode 100644 index 000000000..89a3a28a4 Binary files /dev/null and b/assets/images/mobile/virtualpad/up.png differ diff --git a/assets/images/mobile/virtualpad/v.png b/assets/images/mobile/virtualpad/v.png new file mode 100644 index 000000000..2e9ccf775 Binary files /dev/null and b/assets/images/mobile/virtualpad/v.png differ diff --git a/assets/images/mobile/virtualpad/w.png b/assets/images/mobile/virtualpad/w.png new file mode 100644 index 000000000..3c84a137d Binary files /dev/null and b/assets/images/mobile/virtualpad/w.png differ diff --git a/assets/images/mobile/virtualpad/x.png b/assets/images/mobile/virtualpad/x.png new file mode 100644 index 000000000..231c090b3 Binary files /dev/null and b/assets/images/mobile/virtualpad/x.png differ diff --git a/assets/images/mobile/virtualpad/y.png b/assets/images/mobile/virtualpad/y.png new file mode 100644 index 000000000..4ac65849e Binary files /dev/null and b/assets/images/mobile/virtualpad/y.png differ diff --git a/assets/images/mobile/virtualpad/z.png b/assets/images/mobile/virtualpad/z.png new file mode 100644 index 000000000..99bca74b2 Binary files /dev/null and b/assets/images/mobile/virtualpad/z.png differ diff --git a/key.keystore b/key.keystore new file mode 100644 index 000000000..e21ca6f9c Binary files /dev/null and b/key.keystore differ diff --git a/libs.mobile.xml b/libs.mobile.xml new file mode 100644 index 000000000..bd15485ec --- /dev/null +++ b/libs.mobile.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/project.xml b/project.xml index 6f01bc102..5bdf6eb21 100644 --- a/project.xml +++ b/project.xml @@ -38,7 +38,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -141,7 +141,7 @@
- +
@@ -150,6 +150,8 @@ + + @@ -180,6 +182,8 @@ + +
@@ -198,4 +202,10 @@ + + + + + + diff --git a/source/funkin/backend/MusicBeatState.hx b/source/funkin/backend/MusicBeatState.hx index 6ef650bc9..11089f0bc 100644 --- a/source/funkin/backend/MusicBeatState.hx +++ b/source/funkin/backend/MusicBeatState.hx @@ -12,6 +12,12 @@ import funkin.backend.scripting.ScriptPack; import funkin.backend.system.interfaces.IBeatReceiver; import funkin.backend.system.Conductor; import funkin.options.PlayerSettings; +import mobile.objects.Hitbox; +import mobile.flixel.FlxVirtualPad; +import flixel.FlxCamera; +import flixel.input.actions.FlxActionInput; +import flixel.util.FlxDestroyUtil; +import flixel.util.typeLimit.OneOfTwo; class MusicBeatState extends FlxState implements IBeatReceiver { @@ -107,6 +113,112 @@ class MusicBeatState extends FlxState implements IBeatReceiver return PlayerSettings.player1.controls; inline function get_controlsP2():Controls return PlayerSettings.player2.controls; + + #if TOUCH_CONTROLS + public var hitbox:Hitbox; + public var virtualPad:FlxVirtualPad; + public var camHitbox:FlxCamera; + public var camVPad:FlxCamera; + + var trackedInputsHitbox:Array = []; + var trackedInputsVirtualPad:Array = []; + #end + + public function addVirtualPad(DPad:OneOfTwo, Action:OneOfTwo):Void + { + #if TOUCH_CONTROLS + if (virtualPad != null) + removeVirtualPad(); + + virtualPad = new FlxVirtualPad(DPad, Action); + add(virtualPad); + + controls.setVirtualPadUI(virtualPad, virtualPad.curDPadMode, virtualPad.curActionMode); + trackedInputsVirtualPad = controls.trackedInputsUI; + controls.trackedInputsUI = []; + #end + } + + public function removeVirtualPad():Void + { + #if TOUCH_CONTROLS + if (trackedInputsVirtualPad.length > 0) + controls.removeTouchControlsInput(trackedInputsVirtualPad); + + if (virtualPad != null) + remove(virtualPad); + #end + } + + public function addHitbox(?defaultDrawTarget:Bool = false) { + #if TOUCH_CONTROLS + if (hitbox != null) + removeHitbox(); + + hitbox = new Hitbox(); + controls.setHitBox(hitbox); + + trackedInputsHitbox = controls.trackedInputsNOTES; + controls.trackedInputsNOTES = []; + + camHitbox = new FlxCamera(); + camHitbox.bgColor.alpha = 0; + FlxG.cameras.add(camHitbox, defaultDrawTarget); + + hitbox.cameras = [camHitbox]; + add(hitbox); + #end + } + + public function removeHitbox() { + #if TOUCH_CONTROLS + if(trackedInputsHitbox.length > 0) + controls.removeTouchControlsInput(trackedInputsHitbox); + + if(hitbox != null) + remove(hitbox); + #end + } + + public function addVirtualPadCamera(?defaultDrawTarget:Bool = false) { + #if TOUCH_CONTROLS + if (virtualPad == null) return; + + camVPad = new FlxCamera(); + camVPad.bgColor.alpha = 0; + FlxG.cameras.add(camVPad, defaultDrawTarget); + virtualPad.cameras = [camVPad]; + #end + } + + override function destroy() { + // Touch Controls Related + #if TOUCH_CONTROLS + if(trackedInputsHitbox.length > 0) + controls.removeTouchControlsInput(trackedInputsHitbox); + + if(trackedInputsVirtualPad.length > 0) + controls.removeTouchControlsInput(trackedInputsVirtualPad); + + if(virtualPad != null) + virtualPad = FlxDestroyUtil.destroy(virtualPad); + + if(hitbox != null) + hitbox = FlxDestroyUtil.destroy(hitbox); + + if(camHitbox != null) + camHitbox = FlxDestroyUtil.destroy(camHitbox); + + if(camVPad != null) + camVPad = FlxDestroyUtil.destroy(camVPad); + #end + + // CNE Related + super.destroy(); + graphicCache.destroy(); + call("destroy"); + stateScripts = FlxDestroyUtil.destroy(stateScripts); + } public function new(scriptsAllowed:Bool = true, ?scriptName:String) { super(); @@ -133,6 +245,15 @@ class MusicBeatState extends FlxState implements IBeatReceiver script.remappedNames.set(script.fileName, '$i:${script.fileName}'); stateScripts.add(script); script.load(); + stateScripts.set('setVirtualPadMode', function(DPadMode:String, ActionMode:String, ?addCamera = false){ + #if TOUCH_CONTROLS + if(virtualPad == null) return; + removeVirtualPad(); + addVirtualPad(DPadMode, ActionMode); + if(addCamera) + addVirtualPadCamera(); + #end + }); } } else stateScripts.reload(); @@ -187,6 +308,9 @@ class MusicBeatState extends FlxState implements IBeatReceiver return event; } + public static function getState():MusicBeatState + return cast (FlxG.state, MusicBeatState); + override function update(elapsed:Float) { // TODO: DEBUG MODE!! @@ -244,13 +368,6 @@ class MusicBeatState extends FlxState implements IBeatReceiver event("onResize", EventManager.get(ResizeEvent).recycle(w, h, null, null)); } - public override function destroy() { - super.destroy(); - graphicCache.destroy(); - call("destroy"); - stateScripts = FlxDestroyUtil.destroy(stateScripts); - } - public override function draw() { graphicCache.draw(); var e = event("draw", EventManager.get(DrawEvent).recycle()); diff --git a/source/funkin/backend/MusicBeatSubstate.hx b/source/funkin/backend/MusicBeatSubstate.hx index e2bbb3123..d5a0cad24 100644 --- a/source/funkin/backend/MusicBeatSubstate.hx +++ b/source/funkin/backend/MusicBeatSubstate.hx @@ -10,6 +10,12 @@ import funkin.backend.system.Conductor; import funkin.backend.system.Controls; import funkin.options.PlayerSettings; import flixel.FlxSubState; +import mobile.objects.Hitbox; +import mobile.flixel.FlxVirtualPad; +import flixel.FlxCamera; +import flixel.input.actions.FlxActionInput; +import flixel.util.FlxDestroyUtil; +import flixel.util.typeLimit.OneOfTwo; class MusicBeatSubstate extends FlxSubState implements IBeatReceiver { @@ -91,6 +97,112 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver inline function get_controlsP2():Controls return PlayerSettings.player2.controls; + #if TOUCH_CONTROLS + public var hitbox:Hitbox; + public var virtualPad:FlxVirtualPad; + public var camHitbox:FlxCamera; + public var camVPad:FlxCamera; + + var trackedInputsHitbox:Array = []; + var trackedInputsVirtualPad:Array = []; + #end + + public function addVirtualPad(DPad:OneOfTwo, Action:OneOfTwo) + { + #if TOUCH_CONTROLS + if (virtualPad != null) + removeVirtualPad(); + + virtualPad = new FlxVirtualPad(DPad, Action); + add(virtualPad); + + controls.setVirtualPadUI(virtualPad, virtualPad.curDPadMode, virtualPad.curActionMode); + trackedInputsVirtualPad = controls.trackedInputsUI; + controls.trackedInputsUI = []; + #end + } + + public function removeVirtualPad() + { + #if TOUCH_CONTROLS + if (trackedInputsVirtualPad.length > 0) + controls.removeTouchControlsInput(trackedInputsVirtualPad); + + if (virtualPad != null) + remove(virtualPad); + #end + } + + public function addHitbox(?defaultDrawTarget:Bool = false) { + #if TOUCH_CONTROLS + if (hitbox != null) + removeHitbox(); + + hitbox = new Hitbox(); + controls.setHitBox(hitbox); + + trackedInputsHitbox = controls.trackedInputsNOTES; + controls.trackedInputsNOTES = []; + + camHitbox = new FlxCamera(); + camHitbox.bgColor.alpha = 0; + FlxG.cameras.add(camHitbox, defaultDrawTarget); + + hitbox.cameras = [camHitbox]; + hitbox.visible = false; + add(hitbox); + #end + } + + public function removeHitbox() { + #if TOUCH_CONTROLS + if(trackedInputsHitbox.length > 0) + controls.removeTouchControlsInput(trackedInputsHitbox); + + if(hitbox != null) + remove(hitbox); + #end + } + + public function addVirtualPadCamera(?defaultDrawTarget:Bool = false) { + #if TOUCH_CONTROLS + if (virtualPad == null) return; + + camVPad = new FlxCamera(); + camVPad.bgColor.alpha = 0; + FlxG.cameras.add(camVPad, defaultDrawTarget); + virtualPad.cameras = [camVPad]; + #end + } + + override function destroy() { + // Touch Controls Related + #if TOUCH_CONTROLS + if(trackedInputsHitbox.length > 0) + controls.removeTouchControlsInput(trackedInputsHitbox); + + if(trackedInputsVirtualPad.length > 0) + controls.removeTouchControlsInput(trackedInputsVirtualPad); + + if(virtualPad != null) + virtualPad = FlxDestroyUtil.destroy(virtualPad); + + if(hitbox != null) + hitbox = FlxDestroyUtil.destroy(hitbox); + + if(camHitbox != null) + camHitbox = FlxDestroyUtil.destroy(camHitbox); + + if(camVPad != null) + camVPad = FlxDestroyUtil.destroy(camVPad); + #end + + // CNE Related + super.destroy(); + call("destroy"); + stateScripts = FlxDestroyUtil.destroy(stateScripts); + + } public function new(scriptsAllowed:Bool = true, ?scriptName:String) { super(); @@ -112,6 +224,15 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver script.remappedNames.set(script.fileName, '$i:${script.fileName}'); stateScripts.add(script); script.load(); + stateScripts.set('setVirtualPadMode', function(DPadMode:String, ActionMode:String, ?addCamera = false){ + #if TOUCH_CONTROLS + if(virtualPad == null) return; + removeVirtualPad(); + addVirtualPad(DPadMode, ActionMode); + if(addCamera) + addVirtualPadCamera(); + #end + }); } } else stateScripts.reload(); @@ -169,6 +290,9 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver return event; } + public static function getState():MusicBeatSubstate + return cast (FlxG.state, MusicBeatSubstate); + override function update(elapsed:Float) { // TODO: DEBUG MODE!! @@ -225,12 +349,6 @@ class MusicBeatSubstate extends FlxSubState implements IBeatReceiver event("onResize", EventManager.get(ResizeEvent).recycle(w, h, null, null)); } - public override function destroy() { - super.destroy(); - call("destroy"); - stateScripts = FlxDestroyUtil.destroy(stateScripts); - } - public override function switchTo(nextState:FlxState) { var e = event("onStateSwitch", EventManager.get(StateEvent).recycle(nextState)); if (e.cancelled) diff --git a/source/funkin/backend/assets/AssetsLibraryList.hx b/source/funkin/backend/assets/AssetsLibraryList.hx index 494479970..39658293b 100644 --- a/source/funkin/backend/assets/AssetsLibraryList.hx +++ b/source/funkin/backend/assets/AssetsLibraryList.hx @@ -2,6 +2,7 @@ package funkin.backend.assets; import funkin.backend.assets.IModsAssetLibrary; import lime.utils.AssetLibrary; +import lime.utils.AssetType; class AssetsLibraryList extends AssetLibrary { public var libraries:Array = []; @@ -162,10 +163,27 @@ class AssetsLibraryList extends AssetLibrary { libraries.insert(0, lib); return lib; } + + override public function list(type:String):Array + { + var items = []; + + for (library in libraries) + { + var libraryItems = library.list(type); + + if (libraryItems != null) + { + items = items.concat(libraryItems); + } + } + + return items; + } } enum abstract AssetSource(Null) from Bool from Null to Null { var SOURCE = true; var MODS = false; var BOTH = null; -} \ No newline at end of file +} diff --git a/source/funkin/backend/assets/ModsFolder.hx b/source/funkin/backend/assets/ModsFolder.hx index cd1704ed3..9ae0265bc 100644 --- a/source/funkin/backend/assets/ModsFolder.hx +++ b/source/funkin/backend/assets/ModsFolder.hx @@ -35,11 +35,11 @@ class ModsFolder { /** * Path to the `mods` folder. */ - public static var modsPath:String = "./mods/"; + public static var modsPath:String = #if mobile MobileUtil.getStorageDirectory(true) + #end "mods/"; /** * Path to the `addons` folder. */ - public static var addonsPath:String = "./addons/"; + public static var addonsPath:String = #if mobile MobileUtil.getStorageDirectory(true) + #end "addons/"; /** * If accessing a file as assets/data/global/LIB_mymod.hx should redirect to mymod:assets/data/global.hx @@ -54,6 +54,8 @@ class ModsFolder { * Initialises `mods` folder. */ public static function init() { + if (!FileSystem.exists(modsPath)) FileSystem.createDirectory(modsPath); + if (!FileSystem.exists(addonsPath)) FileSystem.createDirectory(addonsPath); if(!getModsList().contains(Options.lastLoadedMod)) Options.lastLoadedMod = null; } diff --git a/source/funkin/backend/assets/Paths.hx b/source/funkin/backend/assets/Paths.hx index 397838237..7a3919672 100644 --- a/source/funkin/backend/assets/Paths.hx +++ b/source/funkin/backend/assets/Paths.hx @@ -182,7 +182,7 @@ class Paths return FlxAtlasFrames.fromAseprite('$key.png', '$key.json'); inline static public function getAssetsRoot():String - return ModsFolder.currentModFolder != null ? '${ModsFolder.modsPath}${ModsFolder.currentModFolder}' : #if (sys && TEST_BUILD) './${Main.pathBack}assets/' #else './assets' #end; + return ModsFolder.currentModFolder != null ? '${ModsFolder.modsPath}${ModsFolder.currentModFolder}' : #if (sys && !mobile && TEST_BUILD) './${Main.pathBack}assets/' #else './assets' #end; /** * Gets frames at specified path. @@ -340,4 +340,4 @@ class ScriptPathInfo { this.file = file; this.library = library; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/assets/ScriptedAssetLibrary.hx b/source/funkin/backend/assets/ScriptedAssetLibrary.hx index 0fd8d380c..dd08b1d78 100644 --- a/source/funkin/backend/assets/ScriptedAssetLibrary.hx +++ b/source/funkin/backend/assets/ScriptedAssetLibrary.hx @@ -21,7 +21,8 @@ class ScriptedAssetLibrary extends ModsFolderLibrary { public var scriptName:String; private static var nullValue:Dynamic = {}; - public function new(scriptName:String, args:Array = null, folderPath:String="./assets/", libName:String="assets", ?modName:String) { + public function new(scriptName:String, args:Array = null, folderPath:String, libName:String="assets", ?modName:String) { + if(folderPath == null) folderPath = #if mobile MobileUtil.getStorageDirectory() + #end "assets/"; if(modName == null) modName = scriptName; super(folderPath, libName, modName); this.scriptName = scriptName; diff --git a/source/funkin/backend/scripting/HScript.hx b/source/funkin/backend/scripting/HScript.hx index 0597a32f8..043db836c 100644 --- a/source/funkin/backend/scripting/HScript.hx +++ b/source/funkin/backend/scripting/HScript.hx @@ -106,6 +106,10 @@ class HScript extends Script { Logs.logText(fn, GREEN), Logs.logText(err, RED) ], ERROR); + + #if mobile + funkin.backend.utils.NativeAPI.showMessageBox(fn + err, "HSCRIPT ERROR", MSG_ERROR); + #end } public override function setParent(parent:Dynamic) { diff --git a/source/funkin/backend/shaders/CustomShader.hx b/source/funkin/backend/shaders/CustomShader.hx index 9be4300f7..191159427 100644 --- a/source/funkin/backend/shaders/CustomShader.hx +++ b/source/funkin/backend/shaders/CustomShader.hx @@ -19,9 +19,9 @@ class CustomShader extends FunkinShader { /** * Creates a new custom shader * @param name Name of the frag and vert files. - * @param glslVersion GLSL version to use. Defaults to `120`. + * @param glslVersion GLSL version to use. Defaults to `100` in mobile, `120` in desktop. */ - public function new(name:String, glslVersion:String = "120") { + public function new(name:String, glslVersion:String = #if mobile "100" #else "120" #end) { var fragShaderPath = Paths.fragShader(name); var vertShaderPath = Paths.vertShader(name); var fragCode = Assets.exists(fragShaderPath) ? Assets.getText(fragShaderPath) : null; diff --git a/source/funkin/backend/shaders/FunkinShader.hx b/source/funkin/backend/shaders/FunkinShader.hx index 152ca6c35..87da275de 100644 --- a/source/funkin/backend/shaders/FunkinShader.hx +++ b/source/funkin/backend/shaders/FunkinShader.hx @@ -28,7 +28,7 @@ import openfl.display.ShaderInput; class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { private static var __instanceFields = Type.getInstanceFields(FunkinShader); - public var glslVer:String = "120"; + public var glslVer:String = #if lime_opengles "100" #else "120" #end; public var fragFileName:String; public var vertFileName:String; @@ -37,9 +37,9 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { * Accepts `#pragma header`. * @param frag Fragment source (pass `null` to use default) * @param vert Vertex source (pass `null` to use default) - * @param glslVer Version of GLSL to use (defaults to 120) + * @param glslVer Version of GLSL to use (defaults to 120 at OpenGL, 300 es at OpenGL ES) */ - public override function new(frag:String, vert:String, glslVer:String = "120") { + public override function new(frag:String, vert:String, glslVer:String = #if lime_opengles "100" #else "120" #end) { if (frag == null) frag = ShaderTemplates.defaultFragmentSource; if (vert == null) vert = ShaderTemplates.defaultVertexSource; this.glFragmentSource = frag; @@ -229,6 +229,9 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { var gl = __context.gl; + #if (js && html5) + prefixBuf.add(precisionHint == FULL ? "precision mediump float;\n" : "precision lowp float;\n"); + #else prefixBuf.add("#ifdef GL_ES\n"); if (precisionHint == FULL) { prefixBuf.add("#ifdef GL_FRAGMENT_PRECISION_HIGH\n"); @@ -240,6 +243,7 @@ class FunkinShader extends FlxShader implements IHScriptCustomBehaviour { prefixBuf.add("precision lowp float;\n"); } prefixBuf.add("#endif\n"); + #end var prefix = prefixBuf.toString(); @@ -698,4 +702,4 @@ class ShaderErrorPosition { this.column = Std.parseInt(column); this.message = message; } -} \ No newline at end of file +} diff --git a/source/funkin/backend/system/Controls.hx b/source/funkin/backend/system/Controls.hx index a09da1c74..70854daea 100644 --- a/source/funkin/backend/system/Controls.hx +++ b/source/funkin/backend/system/Controls.hx @@ -9,6 +9,9 @@ import flixel.input.actions.FlxActionSet; import flixel.input.gamepad.FlxGamepadButton; import flixel.input.gamepad.FlxGamepadInputID; import flixel.input.keyboard.FlxKey; +import mobile.flixel.FlxButton as Button; +import mobile.objects.Hitbox; +import mobile.flixel.FlxVirtualPad; enum abstract Action(String) to String from String { var UP = "up"; @@ -372,9 +375,12 @@ class Controls extends FlxActionSet inline function set_SWITCHMOD(val) return @:privateAccess _switchMod._checked = val; + public static var instance:Controls; + public function new(name, scheme = None) { super(name); + instance = this; add(_up); add(_left); @@ -419,6 +425,111 @@ class Controls extends FlxActionSet { super.update(); } + + public var touchC(get, never):Bool; + + @:noCompletion + private function get_touchC():Bool + return #if TOUCH_CONTROLS Options.controlsAlpha >= 0.1 #else false #end; + + #if TOUCH_CONTROLS + public var trackedInputsUI:Array = []; + public var trackedInputsNOTES:Array = []; + #end + + public function addButtonNOTES(action:FlxActionDigital, button:Button, state:FlxInputState):Void + { + #if TOUCH_CONTROLS + if (button == null) + return; + + var input:FlxActionInputDigitalIFlxInput = new FlxActionInputDigitalIFlxInput(button, state); + trackedInputsNOTES.push(input); + action.add(input); + #end + } + + public function addButtonUI(action:FlxActionDigital, button:Button, state:FlxInputState):Void + { + #if TOUCH_CONTROLS + if (button == null) + return; + + var input:FlxActionInputDigitalIFlxInput = new FlxActionInputDigitalIFlxInput(button, state); + trackedInputsUI.push(input); + action.add(input); + #end + } + + public function setHitBox(hitbox:Hitbox):Void + { + #if TOUCH_CONTROLS + if (Hitbox == null) + return; + + inline forEachBound(Control.NOTE_LEFT, (action, state) -> addButtonNOTES(action, hitbox.buttonLeft, state)); + inline forEachBound(Control.NOTE_DOWN, (action, state) -> addButtonNOTES(action, hitbox.buttonDown, state)); + inline forEachBound(Control.NOTE_UP, (action, state) -> addButtonNOTES(action, hitbox.buttonUp, state)); + inline forEachBound(Control.NOTE_RIGHT, (action, state) -> addButtonNOTES(action, hitbox.buttonRight, state)); + #end + } + + public function setVirtualPadUI(vpad:FlxVirtualPad, DPad:FlxDPadMode, Action:FlxActionMode):Void + { + #if TOUCH_CONTROLS + if (vpad == null) + return; + + switch (DPad) + { + case UP_DOWN: + inline forEachBound(Control.UP, (action, state) -> addButtonUI(action, vpad.buttonUp, state)); + inline forEachBound(Control.DOWN, (action, state) -> addButtonUI(action, vpad.buttonDown, state)); + case LEFT_RIGHT: + inline forEachBound(Control.LEFT, (action, state) -> addButtonUI(action, vpad.buttonLeft, state)); + inline forEachBound(Control.RIGHT, (action, state) -> addButtonUI(action, vpad.buttonRight, state)); + case NONE: // do nothing + default: + inline forEachBound(Control.UP, (action, state) -> addButtonUI(action, vpad.buttonUp, state)); + inline forEachBound(Control.DOWN, (action, state) -> addButtonUI(action, vpad.buttonDown, state)); + inline forEachBound(Control.LEFT, (action, state) -> addButtonUI(action, vpad.buttonLeft, state)); + inline forEachBound(Control.RIGHT, (action, state) -> addButtonUI(action, vpad.buttonRight, state)); + } + + switch (Action) + { + case A: + inline forEachBound(Control.ACCEPT, (action, state) -> addButtonUI(action, vpad.buttonA, state)); + case B: + inline forEachBound(Control.BACK, (action, state) -> addButtonUI(action, vpad.buttonB, state)); + case P: + inline forEachBound(Control.PAUSE, (action, state) -> addButtonUI(action, vpad.buttonP, state)); + case NONE: // do nothing + default: + inline forEachBound(Control.ACCEPT, (action, state) -> addButtonUI(action, vpad.buttonA, state)); + inline forEachBound(Control.BACK, (action, state) -> addButtonUI(action, vpad.buttonB, state)); + } + #end + } + + public function removeTouchControlsInput(Tinputs:Array):Void + { + #if TOUCH_CONTROLS + for (action in this.digitalActions) + { + var i = action.inputs.length; + while (i-- > 0) + { + var x = Tinputs.length; + while (x-- > 0) + { + if (Tinputs[x] == action.inputs[i]) + action.remove(action.inputs[i]); + } + } + } + #end + } // inline public function checkByName(name:Action):Bool diff --git a/source/funkin/backend/system/Main.hx b/source/funkin/backend/system/Main.hx index 78369f607..72e218b47 100644 --- a/source/funkin/backend/system/Main.hx +++ b/source/funkin/backend/system/Main.hx @@ -24,6 +24,7 @@ import sys.thread.Thread; import sys.io.File; #end import funkin.backend.assets.ModsFolder; +import lime.system.System as LimeSystem; class Main extends Sprite { @@ -38,9 +39,7 @@ class Main extends Sprite public static var noTerminalColor:Bool = false; public static var scaleMode:FunkinRatioScaleMode; - #if !mobile public static var framerateSprite:funkin.backend.system.framerate.Framerate; - #end var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels). var gameHeight:Int = 720; // Height of the game in pixels (might be less / more in actual pixels). @@ -63,12 +62,26 @@ class Main extends Sprite instance = this; + #if mobile + #if android + MobileUtil.requestPermissionsFromUser(); + #end + Sys.setCwd(MobileUtil.getStorageDirectory(false)); + #end + CrashHandler.init(); + #if !web framerateSprite = new funkin.backend.system.framerate.Framerate(); #end + addChild(game = new FunkinGame(gameWidth, gameHeight, MainState, Options.framerate, Options.framerate, skipSplash, startFullscreen)); - #if (!mobile && !web) - addChild(framerateSprite = new funkin.backend.system.framerate.Framerate()); + #if android FlxG.android.preventDefaultKeys = [BACK]; #end + + #if !web + addChild(framerateSprite); + #if mobile + FlxG.stage.window.onResize.add((w:Int, h:Int) -> framerateSprite.setScale()); + #end SystemInfo.init(); #end } @@ -127,12 +140,12 @@ class Main extends Sprite #if (sys && TEST_BUILD) trace("Used cne test / cne build. Switching into source assets."); #if MOD_SUPPORT - ModsFolder.modsPath = './${pathBack}mods/'; - ModsFolder.addonsPath = './${pathBack}addons/'; + ModsFolder.modsPath = Sys.getCwd() + '${pathBack}mods/'; + ModsFolder.addonsPath = Sys.getCwd() + '${pathBack}addons/'; #end - Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './${pathBack}assets/', true)); + Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', Sys.getCwd() + '${pathBack}assets/', true)); #elseif USE_ADAPTED_ASSETS - Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', './assets/', true)); + Paths.assetsTree.__defaultLibraries.push(ModsFolder.loadLibraryFromFolder('assets', Sys.getCwd() + 'assets/', true)); #end @@ -155,7 +168,7 @@ class Main extends Sprite FlxG.signals.preStateSwitch.add(onStateSwitch); FlxG.signals.postStateSwitch.add(onStateSwitchPost); - FlxG.mouse.useSystemCursor = true; + FlxG.mouse.useSystemCursor = !Controls.instance.touchC; #if DARK_MODE_WINDOW if(funkin.backend.utils.NativeAPI.hasVersion("Windows 10")) funkin.backend.utils.NativeAPI.redrawWindowHeader(); #end @@ -166,6 +179,9 @@ class Main extends Sprite #end initTransition(); + #if mobile + LimeSystem.allowScreenTimeout = Options.screenTimeOut; + #end } public static function refreshAssets() { diff --git a/source/funkin/backend/system/MainState.hx b/source/funkin/backend/system/MainState.hx index a5011d6d6..a277d6256 100644 --- a/source/funkin/backend/system/MainState.hx +++ b/source/funkin/backend/system/MainState.hx @@ -8,6 +8,9 @@ import funkin.menus.TitleState; import funkin.menus.BetaWarningState; import funkin.backend.chart.EventsData; import flixel.FlxState; +#if mobile +import mobile.funkin.backend.system.CopyState; +#end /** * Simple state used for loading the game @@ -17,12 +20,24 @@ class MainState extends FlxState { public static var betaWarningShown:Bool = false; public override function create() { super.create(); + #if mobile + funkin.backend.system.Main.framerateSprite.setScale(); + #end if (!initiated) + { Main.loadGameSettings(); + #if mobile + if (!CopyState.checkExistingFiles()) + { + FlxG.switchState(new CopyState()); + return; + } + #end + } initiated = true; #if sys - CoolUtil.deleteFolder('./.temp/'); // delete temp folder + CoolUtil.deleteFolder('.temp/'); // delete temp folder #end Options.save(); diff --git a/source/funkin/backend/system/framerate/Framerate.hx b/source/funkin/backend/system/framerate/Framerate.hx index 88680e89d..a1c6c0fd3 100644 --- a/source/funkin/backend/system/framerate/Framerate.hx +++ b/source/funkin/backend/system/framerate/Framerate.hx @@ -9,6 +9,7 @@ import openfl.display.Sprite; import openfl.text.TextField; import openfl.text.TextFormat; import openfl.ui.Keyboard; +import flixel.util.FlxTimer; class Framerate extends Sprite { public static var instance:Framerate; @@ -43,6 +44,11 @@ class Framerate extends Sprite { return __bitmap; } + #if mobile + #if android public var presses:Int = 0; #end + public var sillyTimer:FlxTimer = new FlxTimer(); + #end + public function new() { super(); if (instance != null) throw "Cannot create another instance"; @@ -101,13 +107,41 @@ class Framerate extends Sprite { public override function __enterFrame(t:Int) { alpha = CoolUtil.fpsLerp(alpha, debugMode > 0 ? 1 : 0, 0.5); debugAlpha = CoolUtil.fpsLerp(debugAlpha, debugMode > 1 ? 1 : 0, 0.5); + #if android + if(FlxG.android.justReleased.BACK){ + sillyTimer.cancel(); + ++presses; + if(presses >= 3){ + debugMode = (debugMode + 1) % 3; + presses = 0; + return; + } + sillyTimer.start(0.3, (tmr:FlxTimer) -> presses = 0); + } + #elseif ios + for(camera in FlxG.cameras.list) { + var pos = FlxG.mouse.getScreenPosition(camera); + if (pos.x >= FlxG.game.x + 10 + offset.x && + pos.x <= FlxG.game.x + offset.x + 80 && + pos.y >= FlxG.game.y + 2 + offset.y && + pos.y <= FlxG.game.y + 2 + offset.y + 60) + { + if(FlxG.mouse.justPressed) + sillyTimer.start(0.4, (tmr:FlxTimer) -> debugMode = (debugMode + 1) % 3); + + if(FlxG.mouse.justReleased) + sillyTimer.cancel(); + } else if(sillyTimer.active && !sillyTimer.finished) + sillyTimer.cancel(); + } + #end if (alpha < 0.05) return; super.__enterFrame(t); bgSprite.alpha = debugAlpha * 0.5; - x = 10 + offset.x; - y = 2 + offset.y; + x = #if mobile FlxG.game.x + #end 10 + offset.x; + y = #if mobile FlxG.game.y + #end 2 + offset.y; var width = Math.max(fpsCounter.width, #if SHOW_BUILD_ON_FPS Math.max(memoryCounter.width, codenameBuildField.width) #else memoryCounter.width #end) + (x*2); var height = #if SHOW_BUILD_ON_FPS codenameBuildField.y + codenameBuildField.height #else memoryCounter.y + memoryCounter.height #end; @@ -132,4 +166,12 @@ class Framerate extends Sprite { y = c.y + c.height + 4; } } -} \ No newline at end of file + + #if mobile + public inline function setScale(?scale:Float){ + if(scale == null) + scale = Math.min(FlxG.stage.window.width / FlxG.width, FlxG.stage.window.height / FlxG.height); + scaleX = scaleY = #if android (scale > 1 ? scale : 1) #else (scale < 1 ? scale : 1) #end; + } + #end +} diff --git a/source/funkin/backend/system/framerate/SystemInfo.hx b/source/funkin/backend/system/framerate/SystemInfo.hx index 0d2bc73a5..8fa383861 100644 --- a/source/funkin/backend/system/framerate/SystemInfo.hx +++ b/source/funkin/backend/system/framerate/SystemInfo.hx @@ -3,9 +3,22 @@ package funkin.backend.system.framerate; import funkin.backend.utils.native.HiddenProcess; import funkin.backend.utils.MemoryUtil; import funkin.backend.system.Logs; +#if android +import android.os.Build; +import android.os.Build.VERSION; +#end using StringTools; +#if cpp +#if windows +@:cppFileCode('#include ') +#elseif (mac || ios) +@:cppFileCode('#include ') +#else +@:headerInclude('sys/utsname.h') +#end +#end class SystemInfo extends FramerateCategory { public static var osInfo:String = "Unknown"; public static var gpuName:String = "Unknown"; @@ -62,7 +75,7 @@ class SystemInfo extends FramerateCategory { if (process.exitCode() != 0) throw 'Could not fetch CPU information'; cpuName = process.stdout.readAll().toString().trim().split("\n")[1].trim(); - #elseif mac + #elseif (mac || ios) var process = new HiddenProcess("sysctl -a | grep brand_string"); // Somehow this isnt able to use the args but it still works if (process.exitCode() != 0) throw 'Could not fetch CPU information'; @@ -77,6 +90,8 @@ class SystemInfo extends FramerateCategory { break; } } + #elseif android + cpuName = (VERSION.SDK_INT >= VERSION_CODES.S) ? Build.SOC_MODEL : Build.HARDWARE; #end } catch (e) { Logs.trace('Unable to grab CPU Name: $e', ERROR, RED); @@ -116,9 +131,9 @@ class SystemInfo extends FramerateCategory { } static function formatSysInfo() { - __formattedSysText = ""; + __formattedSysText = #if android 'Device: ${Build.BRAND.charAt(0).toUpperCase() + Build.BRAND.substring(1)} ${Build.MODEL} (${Build.BOARD})\n' #else "" #end; if (osInfo != "Unknown") __formattedSysText += 'System: $osInfo'; - if (cpuName != "Unknown") __formattedSysText += '\nCPU: $cpuName ${openfl.system.Capabilities.cpuArchitecture} ${(openfl.system.Capabilities.supports64BitProcesses ? '64-Bit' : '32-Bit')}'; + if (cpuName != "Unknown") __formattedSysText += '\nCPU: $cpuName ${getCPUArch()}'; if (gpuName != cpuName || vRAM != "Unknown") { var gpuNameKnown = gpuName != "Unknown" && gpuName != cpuName; var vramKnown = vRAM != "Unknown"; @@ -146,4 +161,44 @@ class SystemInfo extends FramerateCategory { this.text.text = _text; super.__enterFrame(t); } + + #if windows + @:functionCode(' + SYSTEM_INFO osInfo; + + GetSystemInfo(&osInfo); + + switch(osInfo.wProcessorArchitecture) + { + case 9: + return ::String("x86_64"); + case 5: + return ::String("ARM"); + case 12: + return ::String("ARM64"); + case 6: + return ::String("IA-64"); + case 0: + return ::String("x86"); + default: + return ::String("Unknown"); + } + ') + #elseif (mac || ios) + @:functionCode(' + const NXArchInfo *archInfo = NXGetLocalArchInfo(); + return ::String(archInfo == NULL ? "Unknown" : archInfo->name); + ') + #elseif cpp + @:functionCode(' + struct utsname osInfo{}; + uname(&osInfo); + return ::String(osInfo.machine); + ') + #end + @:noCompletion + private static function getCPUArch():String + { + return "Unknown"; + } } \ No newline at end of file diff --git a/source/funkin/backend/system/macros/Macros.hx b/source/funkin/backend/system/macros/Macros.hx index 9183e4a4e..c565a239b 100644 --- a/source/funkin/backend/system/macros/Macros.hx +++ b/source/funkin/backend/system/macros/Macros.hx @@ -18,6 +18,10 @@ class Macros { "flixel.addons.api", "flixel.addons.display", "flixel.addons.effects", "flixel.addons.ui", "flixel.addons.plugin", "flixel.addons.text", "flixel.addons.tile", "flixel.addons.transition", "flixel.addons.util", + // MOBILE + "mobile", + // OPENFL SYSTEM + "openfl.system", // OTHER LIBRARIES & STUFF #if THREE_D_SUPPORT "away3d", "flx3d", #end #if VIDEO_CUTSCENES "hxvlc.flixel", "hxvlc.openfl", #end diff --git a/source/funkin/backend/system/modules/CrashHandler.hx b/source/funkin/backend/system/modules/CrashHandler.hx index 19c183165..57c44f75a 100644 --- a/source/funkin/backend/system/modules/CrashHandler.hx +++ b/source/funkin/backend/system/modules/CrashHandler.hx @@ -1,17 +1,18 @@ package funkin.backend.system.modules; -import lime.system.System; -import funkin.backend.utils.NativeAPI; -import openfl.Lib; import openfl.events.UncaughtErrorEvent; import openfl.events.ErrorEvent; import openfl.errors.Error; -import openfl.events.UncaughtErrorEvent; -import haxe.CallStack; +import lime.system.System as LimeSystem; +import haxe.io.Path; +#if sys +import sys.FileSystem; +import sys.io.File; +#end class CrashHandler { - public static function init() { - Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError); + public static function init():Void { + openfl.Lib.current.loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtError); #if cpp untyped __global__.__hxcpp_set_critical_error_handler(onError); #elseif hl @@ -19,7 +20,11 @@ class CrashHandler { #end } - public static function onUncaughtError(e:UncaughtErrorEvent) { + private static function onUncaughtError(e:UncaughtErrorEvent):Void { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + var m:String = e.error; if (Std.isOfType(e.error, Error)) { var err = cast(e.error, Error); @@ -28,40 +33,52 @@ class CrashHandler { var err = cast(e.error, ErrorEvent); m = '${err.text}'; } - var stack = CallStack.exceptionStack(); - var stackLabel:String = ""; + var stack = haxe.CallStack.exceptionStack(); + var stackBuffer = new StringBuf(); for(e in stack) { switch(e) { - case CFunction: stackLabel += "Non-Haxe (C) Function"; - case Module(c): stackLabel += 'Module ${c}'; + case CFunction: stackBuffer.add("Non-Haxe (C) Function\n"); + case Module(c): stackBuffer.add('Module ${c}\n'); case FilePos(parent, file, line, col): switch(parent) { case Method(cla, func): - stackLabel += '(${file}) ${cla.split(".").last()}.$func() - line $line'; + stackBuffer.add('${Path.withoutExtension(file)}.$func() - line $line\n'); case _: - stackLabel += '(${file}) - line $line'; + stackBuffer.add('${file} - line $line\n'); } case LocalFunction(v): - stackLabel += 'Local Function ${v}'; + stackBuffer.add('Local Function ${v}\n'); case Method(cl, m): - stackLabel += '${cl} - ${m}'; + stackBuffer.add('${cl} - ${m}\n'); } - stackLabel += "\r\n"; } + var stackLabel = stackBuffer.toString(); + #if sys + try + { + if (!FileSystem.exists('crash')) + FileSystem.createDirectory('crash'); - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); + File.saveContent('crash/' + Date.now().toString().replace(' ', '-').replace(':', "'") + '.txt', '$m\n$stackLabel'); + } + catch (e:haxe.Exception) + trace('Couldn\'t save error message. (${e.message})'); + #end - NativeAPI.showMessageBox("Codename Engine Crash Handler", 'Uncaught Error:$m\n\n$stackLabel', MSG_ERROR); - #if sys - Sys.exit(1); + NativeAPI.showMessageBox("Error!", '$m\n$stackLabel', MSG_ERROR); + + #if js + if (FlxG.sound.music != null) + FlxG.sound.music.stop(); + + js.Browser.window.location.reload(true); + #else + LimeSystem.exit(1); #end } #if (cpp || hl) - private static function onError(message:Dynamic):Void - { + private static function onError(message:Dynamic):Void { throw Std.string(message); } #end diff --git a/source/funkin/backend/system/updating/AsyncUpdater.hx b/source/funkin/backend/system/updating/AsyncUpdater.hx index e742e92d0..843c55c21 100644 --- a/source/funkin/backend/system/updating/AsyncUpdater.hx +++ b/source/funkin/backend/system/updating/AsyncUpdater.hx @@ -66,7 +66,7 @@ class AsyncUpdater { var reader = ZipUtil.openZip(path); progress.curZipProgress = new ZipProgress(); - ZipUtil.uncompressZip(reader, './', null, progress.curZipProgress); + ZipUtil.uncompressZip(reader, Sys.getCwd(), null, progress.curZipProgress); // FileSystem.deleteFile(path); } if (executableReplaced = FileSystem.exists('$path$executableName')) { diff --git a/source/funkin/backend/system/updating/UpdateScreen.hx b/source/funkin/backend/system/updating/UpdateScreen.hx index 323fda09c..c9555b383 100644 --- a/source/funkin/backend/system/updating/UpdateScreen.hx +++ b/source/funkin/backend/system/updating/UpdateScreen.hx @@ -121,7 +121,7 @@ class UpdateScreen extends MusicBeatState { #if windows // the executable has been replaced, restart the game entirely Sys.command('start /B ${AsyncUpdater.executableName}'); - #else + #elseif !mobile // We have to make the new executable allowed to execute // before we can execute it! Sys.command('chmod +x ./${AsyncUpdater.executableName} && ./${AsyncUpdater.executableName}'); diff --git a/source/funkin/backend/utils/MemoryUtil.hx b/source/funkin/backend/utils/MemoryUtil.hx index 3facba154..02f6c59cc 100644 --- a/source/funkin/backend/utils/MemoryUtil.hx +++ b/source/funkin/backend/utils/MemoryUtil.hx @@ -69,8 +69,12 @@ class MemoryUtil { return funkin.backend.utils.native.Windows.getTotalRam(); #elseif mac return funkin.backend.utils.native.Mac.getTotalRam(); + #elseif ios + return funkin.backend.utils.native.IOS.getTotalRam(); #elseif linux return funkin.backend.utils.native.Linux.getTotalRam(); + #elseif android + return funkin.backend.utils.native.Android.getTotalRam(); #else return 0; #end @@ -124,11 +128,13 @@ class MemoryUtil { var process = new HiddenProcess("wmic", ["memorychip", "get", "SMBIOSMemoryType"]); if (process.exitCode() == 0) memoryOutput = Std.int(Std.parseFloat(process.stdout.readAll().toString().trim().split("\n")[1])); if (memoryOutput != -1) return memoryMap[memoryOutput]; - #elseif mac + #elseif (mac || ios) var process = new HiddenProcess("system_profiler", ["SPMemoryDataType"]); var reg = ~/Type: (.+)/; reg.match(process.stdout.readAll().toString()); if (process.exitCode() == 0) return reg.matched(1); + #elseif android + // MTODO: Do get mem type for android smh? #elseif linux /*var process = new HiddenProcess("sudo", ["dmidecode", "--type", "17"]); if (process.exitCode() != 0) return "Unknown"; @@ -164,4 +170,4 @@ class MemoryUtil { // Gc.exitGCFreeZone(); #end } -} \ No newline at end of file +} diff --git a/source/funkin/backend/utils/NativeAPI.hx b/source/funkin/backend/utils/NativeAPI.hx index a8753a8a4..ff804f033 100644 --- a/source/funkin/backend/utils/NativeAPI.hx +++ b/source/funkin/backend/utils/NativeAPI.hx @@ -171,6 +171,8 @@ class NativeAPI { public static function showMessageBox(caption:String, message:String, icon:MessageBoxIcon = MSG_WARNING) { #if windows Windows.showMessageBox(caption, message, icon); + #elseif android + android.Tools.showAlertDialog(caption, message, {name: "OK", func: null}, null); #else lime.app.Application.current.window.alert(message, caption); #end diff --git a/source/funkin/backend/utils/ZipUtil.hx b/source/funkin/backend/utils/ZipUtil.hx index eef06a6d7..03358a5ee 100644 --- a/source/funkin/backend/utils/ZipUtil.hx +++ b/source/funkin/backend/utils/ZipUtil.hx @@ -21,7 +21,7 @@ import sys.thread.Thread; using StringTools; // import ZipUtils; ZipUtils.uncompressZip(ZipUtils.openZip("E:\\Desktop\\test\\termination lua.ycemod"), "E:\\Desktop\\test\\uncompressed\\"); -// import ZipUtils; var e = ZipUtils.createZipFile("gjnsdghs.ycemod"); ZipUtils.writeFolderToZip(e, "./mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close(); +// import ZipUtils; var e = ZipUtils.createZipFile("gjnsdghs.ycemod"); ZipUtils.writeFolderToZip(e, Sys.getCwd() + "mods/Friday Night Funkin'/", "Friday Night Funkin'/"); e.flush(); e.close(); class ZipUtil { public static var bannedNames:Array = [".git", ".gitignore", ".github", ".vscode", ".gitattributes", "readme.txt"]; diff --git a/source/funkin/backend/utils/native/Android.hx b/source/funkin/backend/utils/native/Android.hx new file mode 100644 index 000000000..4ba180f7e --- /dev/null +++ b/source/funkin/backend/utils/native/Android.hx @@ -0,0 +1,31 @@ +package funkin.backend.utils.native; + +#if android +class Android +{ + @:functionCode(' + FILE *meminfo = fopen("/proc/meminfo", "r"); + + if(meminfo == NULL) + return -1; + + char line[256]; + while(fgets(line, sizeof(line), meminfo)) + { + int ram; + if(sscanf(line, "MemTotal: %d kB", &ram) == 1) + { + fclose(meminfo); + return (ram / 1024); + } + } + + fclose(meminfo); + return -1; + ') + public static function getTotalRam():Float + { + return 0; + } +} +#end diff --git a/source/funkin/backend/utils/native/IOS.hx b/source/funkin/backend/utils/native/IOS.hx new file mode 100644 index 000000000..6f5c84962 --- /dev/null +++ b/source/funkin/backend/utils/native/IOS.hx @@ -0,0 +1,21 @@ +package funkin.backend.utils.native; + +#if ios +@:cppFileCode("#include ") +class IOS { + @:functionCode(' + int mib [] = { CTL_HW, HW_MEMSIZE }; + int64_t value = 0; + size_t length = sizeof(value); + + if(-1 == sysctl(mib, 2, &value, &length, NULL, 0)) + return -1; // An error occurred + + return value / 1024 / 1024; + ') + public static function getTotalRam():Float + { + return 0; + } +} +#end diff --git a/source/funkin/editors/DebugOptions.hx b/source/funkin/editors/DebugOptions.hx index 8dcfeda7d..d7ebdd493 100644 --- a/source/funkin/editors/DebugOptions.hx +++ b/source/funkin/editors/DebugOptions.hx @@ -26,7 +26,7 @@ class DebugOptions extends TreeMenu { class DebugOptionsScreen extends OptionsScreen { public override function new() { - super("Debug Options", "Use this menu to change debug options."); + super("Debug Options", "Use this menu to change debug options.", null, 'LEFT_FULL', 'A_B'); #if windows add(new TextOption( "Show Console", diff --git a/source/funkin/editors/EditorPicker.hx b/source/funkin/editors/EditorPicker.hx index f210ba927..0d7ae0039 100644 --- a/source/funkin/editors/EditorPicker.hx +++ b/source/funkin/editors/EditorPicker.hx @@ -73,7 +73,9 @@ class EditorPicker extends MusicBeatSubstate { } sprites[0].selected = true; - FlxG.mouse.getScreenPosition(subCam, oldMousePos); + if (!controls.touchC) FlxG.mouse.getScreenPosition(subCam, oldMousePos); + + addVirtualPad('UP_DOWN', 'A_B'); } public override function update(elapsed:Float) { @@ -88,14 +90,14 @@ class EditorPicker extends MusicBeatSubstate { } changeSelection(-FlxG.mouse.wheel + (controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0)); - FlxG.mouse.getScreenPosition(subCam, curMousePos); - if (curMousePos.x != oldMousePos.x || curMousePos.y != oldMousePos.y) { + if (!controls.touchC) FlxG.mouse.getScreenPosition(subCam, curMousePos); + if (!controls.touchC && curMousePos.x != oldMousePos.x || curMousePos.y != oldMousePos.y) { oldMousePos.set(curMousePos.x, curMousePos.y); curSelected = -1; changeSelection(Std.int(curMousePos.y / optionHeight)+1); } - if (controls.ACCEPT || FlxG.mouse.justReleased) { + if (controls.ACCEPT || !controls.touchC && FlxG.mouse.justReleased) { if (options[curSelected].state != null) { selected = true; CoolUtil.playMenuSFX(CONFIRM); diff --git a/source/funkin/editors/SaveSubstate.hx b/source/funkin/editors/SaveSubstate.hx index 7202cf373..628b4072c 100644 --- a/source/funkin/editors/SaveSubstate.hx +++ b/source/funkin/editors/SaveSubstate.hx @@ -44,4 +44,4 @@ class SaveSubstate extends MusicBeatSubstate { typedef SaveSubstateData = { var ?defaultSaveFile:String; var ?saveExt:String; -} \ No newline at end of file +} diff --git a/source/funkin/editors/UIDebugState.hx b/source/funkin/editors/UIDebugState.hx index dd74c90ef..16c4c194d 100644 --- a/source/funkin/editors/UIDebugState.hx +++ b/source/funkin/editors/UIDebugState.hx @@ -7,7 +7,8 @@ class UIDebugState extends UIState { public override function create() { super.create(); - FlxG.mouse.useSystemCursor = FlxG.mouse.visible = true; + FlxG.mouse.useSystemCursor = !controls.touchC; + FlxG.mouse.visible = true; var bg = new FlxSprite().makeSolid(FlxG.width, FlxG.height, 0xFF444444); bg.updateHitbox(); diff --git a/source/funkin/editors/character/CharacterEditor.hx b/source/funkin/editors/character/CharacterEditor.hx index 95da49893..33f62f98a 100644 --- a/source/funkin/editors/character/CharacterEditor.hx +++ b/source/funkin/editors/character/CharacterEditor.hx @@ -296,7 +296,7 @@ class CharacterEditor extends UIState { closeCurrentContextMenu(); openContextMenu(topMenu[2].childs); } - if (FlxG.mouse.pressed) { + if (!controls.touchC && FlxG.mouse.pressed) { nextScroll.set(nextScroll.x - FlxG.mouse.deltaScreenX, nextScroll.y - FlxG.mouse.deltaScreenY); currentCursor = HAND; } else diff --git a/source/funkin/editors/character/CharacterSelection.hx b/source/funkin/editors/character/CharacterSelection.hx index 817acf294..51bd16315 100644 --- a/source/funkin/editors/character/CharacterSelection.hx +++ b/source/funkin/editors/character/CharacterSelection.hx @@ -18,21 +18,31 @@ class CharacterSelection extends EditorTreeMenu var modsList:Array = Character.getList(true); + final button:String = controls.touchC ? 'A' : 'ACCEPT'; + var list:Array = [ for (char in (modsList.length == 0 ? Character.getList(false) : modsList)) - new IconOption(char, "Press ACCEPT to edit this character.", Character.getIconFromCharName(char), + new IconOption(char, "Press " + button + " to edit this character.", Character.getIconFromCharName(char), function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("CharacterEditor: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.switchState(new CharacterEditor(char)); }) ]; list.insert(0, new NewOption("New Character", "New Character", function() { - openSubState(new UIWarningSubstate("New Character: Feature Not Implemented!", "This feature isnt implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ + openSubState(new UIWarningSubstate("New Character: Feature Not Implemented!", "This feature isn't implemented yet. Please wait for more cne updates to have this functional.\n\n\n- Codename Devs", [ {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} ])); })); - main = new OptionsScreen("Character Editor", "Select a character to edit", list); + main = new OptionsScreen("Character Editor", "Select a character to edit", list, 'UP_DOWN', 'A_B'); DiscordUtil.call("onEditorTreeLoaded", ["Character Editor"]); } diff --git a/source/funkin/editors/charter/CharterSelection.hx b/source/funkin/editors/charter/CharterSelection.hx index 6fd97bdcd..414d484ec 100644 --- a/source/funkin/editors/charter/CharterSelection.hx +++ b/source/funkin/editors/charter/CharterSelection.hx @@ -16,6 +16,7 @@ using StringTools; class CharterSelection extends EditorTreeMenu { public var freeplayList:FreeplaySonglist; public var curSong:ChartMetaData; + private final button:String = controls.touchC ? 'A' : 'ACCEPT'; public override function create() { bgType = "charter"; @@ -26,26 +27,50 @@ class CharterSelection extends EditorTreeMenu { freeplayList = FreeplaySonglist.get(false); var list:Array = [ - for(s in freeplayList.songs) new EditorIconOption(s.name, "Press ACCEPT to choose a difficulty to edit.", s.icon, function() { + for(s in freeplayList.songs) new EditorIconOption(s.name, "Press " + button + " to choose a difficulty to edit.", s.icon, function() { curSong = s; var list:Array = [ for(d in s.difficulties) if (d != "") - new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() { + new TextOption(d, "Press " + button + " to edit the chart for the selected difficulty", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("Charter: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.switchState(new Charter(s.name, d)); }) ]; list.push(new NewOption("New Difficulty", "New Difficulty", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("New Difficulty: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); - optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list)); + optionsTree.add(new OptionsScreen(s.name, "Select a difficulty to continue.", list, 'UP_DOWN', 'A_B')); }, s.parsedColor.getDefault(0xFFFFFFFF)) ]; list.insert(0, new NewOption("New Song", "New Song", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("New Song: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.state.openSubState(new SongCreationScreen(saveSong)); })); - main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list); + main = new OptionsScreen("Chart Editor", "Select a song to modify the charts from.", list, 'UP_DOWN', 'A_B'); DiscordUtil.call("onEditorTreeLoaded", ["Chart Editor"]); } @@ -114,15 +139,31 @@ class CharterSelection extends EditorTreeMenu { if (creation.voicesBytes != null) sys.io.File.saveBytes('$songFolder/song/Voices.${Paths.SOUND_EXT}', creation.voicesBytes); #end - var option = new EditorIconOption(creation.meta.name, "Press ACCEPT to choose a difficulty to edit.", creation.meta.icon, function() { + var option = new EditorIconOption(creation.meta.name, "Press " + button + " to choose a difficulty to edit.", creation.meta.icon, function() { curSong = creation.meta; var list:Array = [ for(d in creation.meta.difficulties) - if (d != "") new TextOption(d, "Press ACCEPT to edit the chart for the selected difficulty", function() { + if (d != "") new TextOption(d, "Press " + button + " to edit the chart for the selected difficulty", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("Charter: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.switchState(new Charter(creation.meta.name, d)); }) ]; list.push(new NewOption("New Difficulty", "New Difficulty", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("New Difficulty: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.state.openSubState(new ChartCreationScreen(saveChart)); })); optionsTree.insert(1, new OptionsScreen(creation.meta.name, "Select a difficulty to continue.", list)); @@ -151,7 +192,15 @@ class CharterSelection extends EditorTreeMenu { // Add to List curSong.difficulties.push(name); - var option = new TextOption(name, "Press ACCEPT to edit the chart for the selected difficulty", function() { + var option = new TextOption(name, "Press " + button + " to edit the chart for the selected difficulty", function() { + #if TOUCH_CONTROLS + if (funkin.backend.system.Controls.instance.touchC) + { + openSubState(new UIWarningSubstate("Charter: Touch Not Supported!", "Please connect a keyboard and mouse to access this editor.", [ + {label: "Ok", color: 0xFFFF0000, onClick: function(t) {}} + ])); + } else + #end FlxG.switchState(new Charter(curSong.name, name)); }); optionsTree.members[optionsTree.members.length-1].insert(optionsTree.members[optionsTree.members.length-1].length-1, option); diff --git a/source/funkin/editors/ui/UITextBox.hx b/source/funkin/editors/ui/UITextBox.hx index b12c1770c..7253eacd9 100644 --- a/source/funkin/editors/ui/UITextBox.hx +++ b/source/funkin/editors/ui/UITextBox.hx @@ -72,6 +72,7 @@ class UITextBox extends UISliceSprite implements IUIFocusable { framesOffset = (selected ? 18 : (hovered ? 9 : 0)); @:privateAccess { if (selected) { + FlxG.stage.window.textInputEnabled = true; __wasFocused = true; caretSpr.alpha = (FlxG.game.ticks % 666) >= 333 ? 1 : 0; diff --git a/source/funkin/game/GameOverSubstate.hx b/source/funkin/game/GameOverSubstate.hx index 66b379681..f493fdaf9 100644 --- a/source/funkin/game/GameOverSubstate.hx +++ b/source/funkin/game/GameOverSubstate.hx @@ -89,6 +89,9 @@ class GameOverSubstate extends MusicBeatSubstate DiscordUtil.call("onGameOver", []); gameoverScript.call("postCreate"); + + addVirtualPad('NONE', 'A_B'); + addVirtualPadCamera(); } override function update(elapsed:Float) diff --git a/source/funkin/game/PlayState.hx b/source/funkin/game/PlayState.hx index 1b5bf86c9..2a9e73d14 100644 --- a/source/funkin/game/PlayState.hx +++ b/source/funkin/game/PlayState.hx @@ -552,6 +552,7 @@ class PlayState extends MusicBeatState @:dox(hide) override public function create() { + #if mobile lime.system.System.allowScreenTimeout = false; #end Note.__customNoteTypeExists = []; // SCRIPTING & DATA INITIALIZATION #if REGION @@ -775,6 +776,14 @@ class PlayState extends MusicBeatState #end startingSong = true; + addHitbox(); + #if TOUCH_CONTROLS + hitbox.visible = true; + #end + #if !android + addVirtualPad('NONE', 'P'); + addVirtualPadCamera(); + #end super.create(); @@ -974,6 +983,7 @@ class PlayState extends MusicBeatState public override function destroy() { scripts.call("destroy"); + #if mobile lime.system.System.allowScreenTimeout = Options.screenTimeOut; #end for(g in __cachedGraphics) g.useCount--; @:privateAccess @@ -1049,6 +1059,8 @@ class PlayState extends MusicBeatState { var event = scripts.event("onSubstateOpen", EventManager.get(StateEvent).recycle(SubState)); + #if mobile lime.system.System.allowScreenTimeout = Options.screenTimeOut; #end + if (!postCreated) MusicBeatState.skipTransIn = true; @@ -1074,6 +1086,7 @@ class PlayState extends MusicBeatState override function closeSubState() { var event = scripts.event("onSubstateClose", EventManager.get(StateEvent).recycle(subState)); + #if mobile lime.system.System.allowScreenTimeout = false; #end if (event.cancelled) return; if (paused) @@ -1253,7 +1266,7 @@ class PlayState extends MusicBeatState updateRatingStuff(); - if (controls.PAUSE && startedCountdown && canPause) + if (#if android FlxG.android.justReleased.BACK || #elseif (TOUCH_CONTROLS && !android) virtualPad.buttonP.justPressed || #end controls.PAUSE && startedCountdown && canPause) pauseGame(); if (canAccessDebugMenus) { @@ -1456,6 +1469,9 @@ class PlayState extends MusicBeatState */ public function endSong():Void { + #if TOUCH_CONTROLS + hitbox.visible = false; + #end scripts.call("onSongEnd"); canPause = false; inst.volume = 0; @@ -1497,6 +1513,9 @@ class PlayState extends MusicBeatState * Immediately switches to the next song, or goes back to the Story/Freeplay menu. */ public function nextSong() { + #if TOUCH_CONTROLS + hitbox.visible = false; + #end if (isStoryMode) { campaignScore += songScore; diff --git a/source/funkin/game/cutscenes/DialogueCutscene.hx b/source/funkin/game/cutscenes/DialogueCutscene.hx index 99ba142be..338175bcb 100644 --- a/source/funkin/game/cutscenes/DialogueCutscene.hx +++ b/source/funkin/game/cutscenes/DialogueCutscene.hx @@ -126,7 +126,12 @@ class DialogueCutscene extends Cutscene { super.update(elapsed); dialogueScript.call("update", [elapsed]); - if(controls.ACCEPT) { + var justTouched:Bool = false; + for (touch in FlxG.touches.list) + if (touch.justPressed) + justTouched = true; + + if(justTouched || controls.ACCEPT) { if(dialogueBox.dialogueEnded) next(); else dialogueBox.text.skip(); } diff --git a/source/funkin/game/cutscenes/VideoCutscene.hx b/source/funkin/game/cutscenes/VideoCutscene.hx index de485450e..a6d7ef78b 100644 --- a/source/funkin/game/cutscenes/VideoCutscene.hx +++ b/source/funkin/game/cutscenes/VideoCutscene.hx @@ -92,7 +92,7 @@ class VideoCutscene extends Cutscene { // ZIP PATH: EXPORT // TODO: this but better and more ram friendly - localPath = './.temp/video-${curVideo++}.mp4'; + localPath = '.temp/video-${curVideo++}.mp4'; Main.execAsync(function() { File.saveBytes(localPath, Assets.getBytes(path)); videoReady = true; diff --git a/source/funkin/import.hx b/source/funkin/import.hx index eca238fd6..6040eb342 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -11,6 +11,8 @@ import funkin.options.Options; import funkin.game.PlayState; import funkin.backend.scripting.EventManager; +import mobile.funkin.backend.utils.MobileUtil; + import openfl.utils.Assets; import flixel.FlxSprite; diff --git a/source/funkin/menus/BetaWarningState.hx b/source/funkin/menus/BetaWarningState.hx index 162014540..f9f512422 100644 --- a/source/funkin/menus/BetaWarningState.hx +++ b/source/funkin/menus/BetaWarningState.hx @@ -19,7 +19,7 @@ class BetaWarningState extends MusicBeatState { disclaimer = new FunkinText(16, titleAlphabet.y + titleAlphabet.height + 10, FlxG.width - 32, "", 32); disclaimer.alignment = CENTER; - disclaimer.applyMarkup('This engine is still in a *${Main.releaseCycle}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\nPress ENTER to continue', + disclaimer.applyMarkup('This engine is still in a *${Main.releaseCycle}* state. That means *majority of the features* are either *buggy* or *non finished*. If you find any bugs, please report them to the Codename Engine GitHub.\n\n${controls.touchC ? 'Tap Your Screen' : 'Press ENTER'} to continue', [ new FlxTextFormatMarkerPair(new FlxTextFormat(0xFFFF4444), "*") ] @@ -36,6 +36,22 @@ class BetaWarningState extends MusicBeatState { public override function update(elapsed:Float) { super.update(elapsed); + #if FLX_TOUCH + for (touch in FlxG.touches.list) + { + if (touch.justPressed && transitioning) { + FlxG.camera.stopFX(); FlxG.camera.visible = false; + goToTitle(); + } else if (touch.justPressed && !transitioning) { + transitioning = true; + CoolUtil.playMenuSFX(CONFIRM); + FlxG.camera.flash(FlxColor.WHITE, 1, function() { + FlxG.camera.fade(FlxColor.BLACK, 2.5, false, goToTitle); + }); + } + } + #end + if (controls.ACCEPT && transitioning) { FlxG.camera.stopFX(); FlxG.camera.visible = false; goToTitle(); diff --git a/source/funkin/menus/FreeplayState.hx b/source/funkin/menus/FreeplayState.hx index 96aab829d..9ff666462 100644 --- a/source/funkin/menus/FreeplayState.hx +++ b/source/funkin/menus/FreeplayState.hx @@ -108,6 +108,9 @@ class FreeplayState extends MusicBeatState curSelected = k; } } + + #if mobile if (funkin.backend.assets.ModsFolder.currentModFolder == null) for (song in songs) song.difficulties = ['EASY', 'NORMAL', 'HARD']; #end // mobile temporary fix + if (songs[curSelected] != null) { for(k=>diff in songs[curSelected].difficulties) { if (diff == Options.freeplayLastDifficulty) { @@ -172,6 +175,8 @@ class FreeplayState extends MusicBeatState changeCoopMode(0, true); interpColor = new FlxInterpolateColor(bg.color); + + addVirtualPad('LEFT_FULL', 'A_B_X_Y'); } #if PRELOAD_ALL @@ -218,7 +223,7 @@ class FreeplayState extends MusicBeatState if (canSelect) { changeSelection((controls.UP_P ? -1 : 0) + (controls.DOWN_P ? 1 : 0) - FlxG.mouse.wheel); changeDiff((controls.LEFT_P ? -1 : 0) + (controls.RIGHT_P ? 1 : 0)); - changeCoopMode((FlxG.keys.justPressed.TAB ? 1 : 0)); + changeCoopMode(((#if TOUCH_CONTROLS virtualPad.buttonX.justPressed || #end FlxG.keys.justPressed.TAB) ? 1 : 0)); // putting it before so that its actually smooth updateOptionsAlpha(); } @@ -256,7 +261,7 @@ class FreeplayState extends MusicBeatState } #if sys - if (FlxG.keys.justPressed.EIGHT && Sys.args().contains("-livereload")) + if (#if TOUCH_CONTROLS virtualPad.buttonY.justPressed || #end FlxG.keys.justPressed.EIGHT && Sys.args().contains("-livereload")) convertChart(); #end @@ -346,7 +351,8 @@ class FreeplayState extends MusicBeatState /** * Array containing all labels for Co-Op / Opponent modes. */ - public var coopLabels:Array = [ + public var coopLabels:Array = controls.touchC ? ["[X] Solo", "[X] Opponent Mode"] : + [ "[TAB] Solo", "[TAB] Opponent Mode", "[TAB] Co-Op Mode", @@ -363,7 +369,13 @@ class FreeplayState extends MusicBeatState if (!songs[curSelected].coopAllowed && !songs[curSelected].opponentModeAllowed) return; var bothEnabled = songs[curSelected].coopAllowed && songs[curSelected].opponentModeAllowed; - var event = event("onChangeCoopMode", EventManager.get(MenuChangeEvent).recycle(curCoopMode, FlxMath.wrap(curCoopMode + change, 0, bothEnabled ? 3 : 1), change)); + var changeThingy:Int = -1; + if(controls.touchC) + changeThingy = FlxMath.wrap(curCoopMode + change, 0, 1); + else + changeThingy = FlxMath.wrap(curCoopMode + change, 0, bothEnabled ? 3 : 1); + + var event = event("onChangeCoopMode", EventManager.get(MenuChangeEvent).recycle(curCoopMode, changeThingy, change)); if (event.cancelled) return; @@ -455,4 +467,4 @@ class FreeplaySonglist { return songList; } -} \ No newline at end of file +} diff --git a/source/funkin/menus/GitarooPause.hx b/source/funkin/menus/GitarooPause.hx index cff8aec9f..652736572 100644 --- a/source/funkin/menus/GitarooPause.hx +++ b/source/funkin/menus/GitarooPause.hx @@ -46,6 +46,8 @@ class GitarooPause extends MusicBeatState changeThing(); super.create(); + + addVirtualPad('LEFT_RIGHT', 'A'); } override function update(elapsed:Float) diff --git a/source/funkin/menus/MainMenuState.hx b/source/funkin/menus/MainMenuState.hx index 968494f17..ef5652aee 100644 --- a/source/funkin/menus/MainMenuState.hx +++ b/source/funkin/menus/MainMenuState.hx @@ -75,13 +75,16 @@ class MainMenuState extends MusicBeatState } FlxG.camera.follow(camFollow, null, 0.06); + var modsKey:String = controls.touchC ? "M" : controls.getKeyName(SWITCHMOD); - versionText = new FunkinText(5, FlxG.height - 2, 0, 'Codename Engine v${Application.current.meta.get('version')}\nCommit ${funkin.backend.system.macros.GitCommitMacro.commitNumber} (${funkin.backend.system.macros.GitCommitMacro.commitHash})\n[${controls.getKeyName(SWITCHMOD)}] Open Mods menu\n'); + versionText = new FunkinText(5, FlxG.height - 2, 0, 'Codename Engine v${Application.current.meta.get('version')}\nCommit ${funkin.backend.system.macros.GitCommitMacro.commitNumber} (${funkin.backend.system.macros.GitCommitMacro.commitHash})\n[$modsKey] Open Mods menu\n'); versionText.y -= versionText.height; versionText.scrollFactor.set(); add(versionText); changeItem(); + + addVirtualPad('UP_DOWN', 'A_B_M_E'); } var selectedSomethin:Bool = false; @@ -95,7 +98,7 @@ class MainMenuState extends MusicBeatState if (!selectedSomethin) { if (canAccessDebugMenus) { - if (FlxG.keys.justPressed.SEVEN) { + if (FlxG.keys.justPressed.SEVEN #if TOUCH_CONTROLS || virtualPad.buttonE.justPressed #end) { persistentUpdate = false; persistentDraw = true; openSubState(new funkin.editors.EditorPicker()); @@ -120,7 +123,7 @@ class MainMenuState extends MusicBeatState FlxG.switchState(new TitleState()); #if MOD_SUPPORT - if (controls.SWITCHMOD) { + if (controls.SWITCHMOD #if TOUCH_CONTROLS || virtualPad.buttonM.justPressed #end) { openSubState(new ModSwitchMenu()); persistentUpdate = false; persistentDraw = true; @@ -140,6 +143,12 @@ class MainMenuState extends MusicBeatState }); } + override function closeSubState() { + super.closeSubState(); + removeVirtualPad(); + addVirtualPad('UP_DOWN', 'A_B_M_E'); + } + public override function switchTo(nextState:FlxState):Bool { try { menuItems.forEach(function(spr:FlxSprite) { diff --git a/source/funkin/menus/ModSwitchMenu.hx b/source/funkin/menus/ModSwitchMenu.hx index eac992dc9..4bfe63093 100644 --- a/source/funkin/menus/ModSwitchMenu.hx +++ b/source/funkin/menus/ModSwitchMenu.hx @@ -34,6 +34,9 @@ class ModSwitchMenu extends MusicBeatSubstate { } add(alphabets); changeSelection(0, true); + + addVirtualPad('UP_DOWN', 'A_B'); + addVirtualPadCamera(); // dawg wtf } public override function update(elapsed:Float) { diff --git a/source/funkin/menus/PauseSubState.hx b/source/funkin/menus/PauseSubState.hx index ecf2fa7da..a55d6e828 100644 --- a/source/funkin/menus/PauseSubState.hx +++ b/source/funkin/menus/PauseSubState.hx @@ -45,6 +45,9 @@ class PauseSubState extends MusicBeatSubstate if (menuItems.contains("Exit to charter") && !PlayState.chartingMode) menuItems.remove("Exit to charter"); + if (controls.touchC) + menuItems.remove("Change Controls"); + add(parentDisabler = new FunkinParentDisabler()); pauseScript = Script.create(Paths.script(script)); @@ -109,6 +112,9 @@ class PauseSubState extends MusicBeatSubstate pauseScript.call("postCreate"); game.updateDiscordPresence(); + + addVirtualPad('UP_DOWN', 'A'); + addVirtualPadCamera(); } override function update(elapsed:Float) @@ -151,6 +157,7 @@ class PauseSubState extends MusicBeatSubstate FlxG.resetState(); case "Change Controls": persistentDraw = false; + removeVirtualPad(); openSubState(new KeybindsOptions()); case "Change Options": TreeMenu.lastState = PlayState; @@ -188,6 +195,14 @@ class PauseSubState extends MusicBeatSubstate super.destroy(); } + override function closeSubState() { + persistentUpdate = true; + super.closeSubState(); + removeVirtualPad(); + addVirtualPad('UP_DOWN', 'A'); + addVirtualPadCamera(); + } + function changeSelection(change:Int = 0):Void { var event = EventManager.get(MenuChangeEvent).recycle(curSelected, FlxMath.wrap(curSelected + change, 0, menuItems.length-1), change, change != 0); diff --git a/source/funkin/menus/PlaytestingWarningSubstate.hx b/source/funkin/menus/PlaytestingWarningSubstate.hx index e366bf9f1..f9b5aa469 100644 --- a/source/funkin/menus/PlaytestingWarningSubstate.hx +++ b/source/funkin/menus/PlaytestingWarningSubstate.hx @@ -68,6 +68,8 @@ class PlaytestingWarningSubstate extends MusicBeatSubstate curSelected = options.length-1; changeSelection(0); + + addVirtualPad('LEFT_RIGHT', 'A'); } var sinner:Float = 0; diff --git a/source/funkin/menus/StoryMenuState.hx b/source/funkin/menus/StoryMenuState.hx index 847d921e2..65c0c6fdf 100644 --- a/source/funkin/menus/StoryMenuState.hx +++ b/source/funkin/menus/StoryMenuState.hx @@ -124,6 +124,8 @@ class StoryMenuState extends MusicBeatState { DiscordUtil.call("onMenuLoaded", ["Story Menu"]); CoolUtil.playMenuSong(); + + addVirtualPad('LEFT_FULL', 'A_B'); } var __lastDifficultyTween:FlxTween; diff --git a/source/funkin/menus/TitleState.hx b/source/funkin/menus/TitleState.hx index 4aca30dbf..c8cd03f6d 100644 --- a/source/funkin/menus/TitleState.hx +++ b/source/funkin/menus/TitleState.hx @@ -121,14 +121,10 @@ class TitleState extends MusicBeatState var pressedEnter:Bool = FlxG.keys.justPressed.ENTER; - #if mobile + #if FLX_TOUCH for (touch in FlxG.touches.list) - { if (touch.justPressed) - { pressedEnter = true; - } - } #end var gamepad:FlxGamepad = FlxG.gamepads.lastActive; @@ -387,4 +383,4 @@ typedef TitleStateImage = { @:optional var scale:Null; @:optional var flipX:Null; @:optional var flipY:Null; -} \ No newline at end of file +} diff --git a/source/funkin/menus/credits/CreditsCodename.hx b/source/funkin/menus/credits/CreditsCodename.hx index f5929f181..d3daaa8d0 100644 --- a/source/funkin/menus/credits/CreditsCodename.hx +++ b/source/funkin/menus/credits/CreditsCodename.hx @@ -19,7 +19,7 @@ class CreditsCodename extends funkin.options.OptionsScreen { public override function new() { - super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes)."); + super("Codename Engine", "All the contributors of the engine! - Press RESET to update the list (One reset per 2 minutes).", null, 'UP_DOWN', 'A_B'); tryUpdating(true); } diff --git a/source/funkin/menus/credits/CreditsMain.hx b/source/funkin/menus/credits/CreditsMain.hx index 847d2b647..02143bc10 100644 --- a/source/funkin/menus/credits/CreditsMain.hx +++ b/source/funkin/menus/credits/CreditsMain.hx @@ -42,7 +42,8 @@ class CreditsMain extends TreeMenu { CoolUtil.openURL("https://ninja-muffin24.itch.io/funkin"); })); - main = new OptionsScreen('Credits', 'The people who made this possible!', items); + main = new OptionsScreen('Credits', 'The people who made this possible!', items, 'UP_DOWN', 'A_B'); + super.create(); DiscordUtil.call("onMenuLoaded", ["Credits Menu"]); @@ -96,7 +97,7 @@ class CreditsMain extends TreeMenu { case "menu": credsMenus.push(new TextOption(name + " >", desc, function() { - optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source))); + optionsTree.add(new OptionsScreen(name, desc, parseCreditsFromXML(node, source), 'UP_DOWN', 'A_B')); })); } } diff --git a/source/funkin/options/Options.hx b/source/funkin/options/Options.hx index a9bf72ac1..7d0d0140f 100644 --- a/source/funkin/options/Options.hx +++ b/source/funkin/options/Options.hx @@ -33,15 +33,26 @@ class Options public static var splashesEnabled:Bool = true; public static var hitWindow:Float = 250; public static var songOffset:Float = 0; - public static var framerate:Int = 120; - public static var gpuOnlyBitmaps:Bool = #if (mac || web) false #else true #end; // causes issues on mac and web + public static var framerate:Int = #if !mobile 120 #else 60 #end; + public static var gpuOnlyBitmaps:Bool = #if (mac || web || mobile) false #else true #end; // causes issues on mac, web and mobile public static var lastLoadedMod:String = null; + /** + * MOBILE SETTINGS + */ + #if mobile + public static var screenTimeOut:Bool = false; + #end + public static var hideHitbox:Bool = false; + public static var hitboxType:String = 'gradient'; + public static var controlsAlpha:Float = FlxG.onMobile ? 0.6 : 0; + #if android public static var storageType:String = "EXTERNAL_DATA"; #end + /** * EDITORS SETTINGS */ - public static var intensiveBlur:Bool = true; + public static var intensiveBlur:Bool = #if mobile false #else true #end; public static var editorSFX:Bool = true; public static var editorPrettyPrint:Bool = false; public static var maxUndos:Int = 120; diff --git a/source/funkin/options/OptionsMenu.hx b/source/funkin/options/OptionsMenu.hx index 497bfd9b9..8b895cdda 100644 --- a/source/funkin/options/OptionsMenu.hx +++ b/source/funkin/options/OptionsMenu.hx @@ -5,6 +5,8 @@ import haxe.xml.Access; import funkin.options.type.*; import funkin.options.categories.*; import funkin.options.TreeMenu; +import haxe.ds.Map; +import mobile.flixel.FlxVirtualPad; class OptionsMenu extends TreeMenu { public static var mainOptions:Array = [ @@ -24,6 +26,11 @@ class OptionsMenu extends TreeMenu { desc: 'Change Appearance options such as Flashing menus...', state: AppearanceOptions }, + { + name: 'Mobile Options >', + desc: 'Change Options Related To Mobile & Touch Controls', + state: MobileOptions + }, { name: 'Miscellaneous >', desc: 'Use this menu to reset save data or engine settings.', @@ -34,6 +41,13 @@ class OptionsMenu extends TreeMenu { public override function create() { super.create(); + if (funkin.backend.system.Controls.instance.touchC) + { + mainOptions = mainOptions.filter(function(option) { + return option.name != "Controls"; + }); + } + CoolUtil.playMenuSong(); DiscordUtil.call("onMenuLoaded", ["Options Menu"]); @@ -80,7 +94,8 @@ class OptionsMenu extends TreeMenu { main.add(o); } } - + addVirtualPad('UP_DOWN', 'A_B'); + addVirtualPadCamera(); } public override function exit() { @@ -92,6 +107,7 @@ class OptionsMenu extends TreeMenu { /** * XML STUFF */ + var vpadMap:Map> = new Map(); public function parseOptionsFromXML(xml:Access):Array { var options:Array = []; @@ -136,8 +152,16 @@ class OptionsMenu extends TreeMenu { case "menu": options.push(new TextOption(name + " >", desc, function() { - optionsTree.add(new OptionsScreen(name, desc, parseOptionsFromXML(node))); + optionsTree.add(new OptionsScreen(name, desc, parseOptionsFromXML(node), vpadMap.exists(name) ? vpadMap.get(name)[0] : 'NONE', vpadMap.exists(name) ? vpadMap.get(name)[1] : 'NONE')); })); + case "virtualPad": + #if TOUCH_CONTROLS + var arr = [ + node.getAtt("dpadMode") == null ? MusicBeatState.getState().virtualPad.curDPadMode.getName() : node.getAtt("dpadMode"), + node.getAtt("actionMode") == null ? MusicBeatState.getState().virtualPad.curActionMode.getName() : node.getAtt("actionMode") + ]; + vpadMap.set(node.getAtt("menuName"), arr); + #end } } diff --git a/source/funkin/options/OptionsScreen.hx b/source/funkin/options/OptionsScreen.hx index 455808215..77d3e1063 100644 --- a/source/funkin/options/OptionsScreen.hx +++ b/source/funkin/options/OptionsScreen.hx @@ -15,11 +15,24 @@ class OptionsScreen extends FlxTypedSpriteGroup { public var name:String; public var desc:String; - public function new(name:String, desc:String, ?options:Array) { + public var dpadMode:String = 'NONE'; + public var actionMode:String = 'NONE'; + public var prevVPadModes:Array = []; + + public function new(name:String, desc:String, ?options:Array, dpadMode:String = 'NONE', actionMode:String = 'NONE') { super(); this.name = name; this.desc = desc; if (options != null) for(o in options) add(o); + #if TOUCH_CONTROLS + if(MusicBeatState.getState().virtualPad != null) + prevVPadModes = [MusicBeatState.getState().virtualPad.curDPadMode.getName(), MusicBeatState.getState().virtualPad.curActionMode.getName()]; + this.dpadMode = dpadMode; + this.actionMode = actionMode; + MusicBeatState.getState().removeVirtualPad(); + MusicBeatState.getState().addVirtualPad(dpadMode, actionMode); + MusicBeatState.getState().addVirtualPadCamera(); + #end } public override function update(elapsed:Float) { @@ -45,19 +58,24 @@ class OptionsScreen extends FlxTypedSpriteGroup { if (members.length > 0) { members[curSelected].selected = true; - if (controls.ACCEPT || FlxG.mouse.justReleased) + if (controls.ACCEPT || (FlxG.mouse.justReleased && !controls.touchC)) members[curSelected].onSelect(); if (controls.LEFT_P) members[curSelected].onChangeSelection(-1); if (controls.RIGHT_P) members[curSelected].onChangeSelection(1); } - if (controls.BACK || FlxG.mouse.justReleasedRight) + if (controls.BACK || (FlxG.mouse.justReleasedRight && !controls.touchC)) close(); } public function close() { onClose(this); + if(prevVPadModes.length > 0){ + MusicBeatState.getState().removeVirtualPad(); + MusicBeatState.getState().addVirtualPad(prevVPadModes[0], prevVPadModes[1]); + MusicBeatState.getState().addVirtualPadCamera(); + } } public function changeSelection(sel:Int, force:Bool = false) { @@ -75,4 +93,4 @@ class OptionsScreen extends FlxTypedSpriteGroup { } public dynamic function onClose(o:OptionsScreen) {} -} \ No newline at end of file +} diff --git a/source/funkin/options/categories/AppearanceOptions.hx b/source/funkin/options/categories/AppearanceOptions.hx index 926213431..de9ab5c52 100644 --- a/source/funkin/options/categories/AppearanceOptions.hx +++ b/source/funkin/options/categories/AppearanceOptions.hx @@ -2,7 +2,7 @@ package funkin.options.categories; class AppearanceOptions extends OptionsScreen { public override function new() { - super("Appearance", "Change Appearance options such as Flashing menus..."); + super("Appearance", "Change Appearance options such as Flashing menus...", null, 'LEFT_FULL', 'A_B'); add(new NumOption( "Framerate", "Pretty self explanatory, isn't it?", diff --git a/source/funkin/options/categories/GameplayOptions.hx b/source/funkin/options/categories/GameplayOptions.hx index 15c0ba603..92e2b5fae 100644 --- a/source/funkin/options/categories/GameplayOptions.hx +++ b/source/funkin/options/categories/GameplayOptions.hx @@ -9,7 +9,7 @@ class GameplayOptions extends OptionsScreen { var offsetSetting:NumOption; public override function new() { - super("Gameplay", 'Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...'); + super("Gameplay", 'Change Gameplay options such as Downscroll, Scroll Speed, Naughtyness...', null, 'LEFT_FULL', 'A_B'); add(new Checkbox( "Downscroll", "If checked, notes will go from up to down instead of down to up, as if they're falling.", diff --git a/source/funkin/options/categories/MiscOptions.hx b/source/funkin/options/categories/MiscOptions.hx index 2aecb20fd..1287587d9 100644 --- a/source/funkin/options/categories/MiscOptions.hx +++ b/source/funkin/options/categories/MiscOptions.hx @@ -3,7 +3,7 @@ package funkin.options.categories; class MiscOptions extends OptionsScreen { public override function new() { - super("Miscellaneous", "Use this menu to reset save data or engine settings."); + super("Miscellaneous", "Use this menu to reset save data or engine settings.", null, #if UPDATE_CHECKING 'UP_DOWN' #else 'NONE' #end, 'A_B'); #if UPDATE_CHECKING add(new Checkbox( "Enable Nightly Updates", diff --git a/source/funkin/options/categories/MobileOptions.hx b/source/funkin/options/categories/MobileOptions.hx new file mode 100644 index 000000000..414af3241 --- /dev/null +++ b/source/funkin/options/categories/MobileOptions.hx @@ -0,0 +1,117 @@ +package funkin.options.categories; + +import flixel.FlxG; +import flixel.input.keyboard.FlxKey; +import flixel.util.FlxTimer; +import funkin.backend.MusicBeatState; +import funkin.options.Options; +import lime.system.System as LimeSystem; +#if android +import mobile.funkin.backend.utils.MobileUtil; +#end +#if sys +import sys.io.File; +#end + +class MobileOptions extends OptionsScreen { + var canEnter:Bool = true; + #if android + final lastStorageType:String = Options.storageType; + var externalPaths:Array = MobileUtil.checkExternalPaths(true); + var typeNames:Array = ['Data', 'Obb', 'Media', 'External']; + var typeVars:Array = ['EXTERNAL_DATA', 'EXTERNAL_OBB', 'EXTERNAL_MEDIA', 'EXTERNAL']; + #end + + public override function new() { + #if android + if (!externalPaths.contains('\n')) + { + typeNames = typeNames.concat(externalPaths); + typeVars = typeVars.concat(externalPaths); + } + #end + dpadMode = 'LEFT_FULL'; + actionMode = 'A_B'; + super("Mobile", 'Change Mobile Related Things such as Controls alpha, screen timeout....', null, 'LEFT_FULL', 'A_B'); + #if TOUCH_CONTROLS + add(new NumOption( + "Controls Alpha", + "Change how transparent the touch controls should be", + 0.0, // minimum + 1.0, // maximum + 0.1, // change + "controlsAlpha", // save name or smth + changeControlsAlpha)); // callback + add(new ArrayOption( + "Hitbox Design", + "Choose how your hitbox should look like!", + ['noGradient', 'noGradientOld', 'gradient', 'hidden'], + ["No Gradient", "No Gradient (Old)", "Gradient", "Hidden"], + 'hitboxType')); + #end + #if mobile + add(new Checkbox( + "Allow Screen Timeout", + "If checked, The phone will enter sleep mode if the player is inactive.", + "screenTimeOut")); + #end + #if android + add(new ArrayOption( + "Storage Type", + "Choose which folder Codename Engine should use! (CHANGING THIS MAKES DELETE YOUR OLD FOLDER!!)", + typeVars, + typeNames, + 'storageType')); + #end + } + + override function update(elapsed) { + #if mobile + final lastScreenTimeOut:Bool = Options.screenTimeOut; + if (lastScreenTimeOut != Options.screenTimeOut) LimeSystem.allowScreenTimeout = Options.screenTimeOut; + #end + super.update(elapsed); + } + + override public function destroy() { + #if android + if (lastStorageType != Options.storageType) { + onStorageChange(); + funkin.backend.utils.NativeAPI.showMessageBox('Notice!', 'Storage Type has been changed and you needed restart the game!!\nPress OK to close the game.'); + LimeSystem.exit(0); + } + #end + } + + function changeControlsAlpha(alpha) { + #if TOUCH_CONTROLS + MusicBeatState.getState().virtualPad.alpha = alpha; + if (funkin.backend.system.Controls.instance.touchC) { + FlxG.sound.volumeUpKeys = []; + FlxG.sound.volumeDownKeys = []; + FlxG.sound.muteKeys = []; + } else { + FlxG.sound.volumeUpKeys = [FlxKey.PLUS, FlxKey.NUMPADPLUS]; + FlxG.sound.volumeDownKeys = [FlxKey.MINUS, FlxKey.NUMPADMINUS]; + FlxG.sound.muteKeys = [FlxKey.ZERO, FlxKey.NUMPADZERO]; + } + #end + } + + #if android + function onStorageChange():Void + { + File.saveContent(LimeSystem.applicationStorageDirectory + 'storagetype.txt', Options.storageType); + + var lastStoragePath:String = StorageType.fromStrForce(lastStorageType) + '/'; + + try + { + if (Options.storageType != "EXTERNAL") + Sys.command('rm', ['-rf', lastStoragePath]); + } + catch (e:haxe.Exception) + trace('Failed to remove last directory. (${e.message})'); + } + #end +} diff --git a/source/funkin/options/keybinds/KeybindsOptions.hx b/source/funkin/options/keybinds/KeybindsOptions.hx index e4e7d26cd..0d5af6c33 100644 --- a/source/funkin/options/keybinds/KeybindsOptions.hx +++ b/source/funkin/options/keybinds/KeybindsOptions.hx @@ -162,6 +162,9 @@ class KeybindsOptions extends MusicBeatSubstate { } add(alphabets); add(camFollow); + + addVirtualPad('LEFT_FULL', 'A_B'); + addVirtualPadCamera(); } public override function destroy() { diff --git a/source/mobile/flixel/FlxButton.hx b/source/mobile/flixel/FlxButton.hx new file mode 100644 index 000000000..b6e4482a1 --- /dev/null +++ b/source/mobile/flixel/FlxButton.hx @@ -0,0 +1,529 @@ +package mobile.flixel; + +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.graphics.atlas.FlxAtlas; +import flixel.graphics.atlas.FlxNode; +import flixel.graphics.frames.FlxTileFrames; +import flixel.input.FlxInput; +import flixel.input.FlxPointer; +import flixel.input.IFlxInput; +import flixel.input.touch.FlxTouch; +import flixel.math.FlxPoint; +import flixel.sound.FlxSound; +import flixel.text.FlxText; +import flixel.util.FlxDestroyUtil; + +/** + * A simple button class that calls a function when clicked by the touch. + */ +class FlxButton extends FlxTypedButton +{ + /** + * Used with public variable status, means not highlighted or pressed. + */ + public static inline var NORMAL:Int = 0; + + /** + * Used with public variable status, means highlighted (usually from touch over). + */ + public static inline var HIGHLIGHT:Int = 1; + + /** + * Used with public variable status, means pressed (usually from touch click). + */ + public static inline var PRESSED:Int = 2; + + /** + * Shortcut to setting label.text + */ + public var text(get, set):String; + + /** + * Creates a new `FlxButton` object with a gray background + * and a callback function on the UI thread. + * + * @param X The x position of the button. + * @param Y The y position of the button. + * @param Text The text that you want to appear on the button. + * @param OnClick The function to call whenever the button is clicked. + */ + public function new(X:Float = 0, Y:Float = 0, ?Text:String, ?OnClick:Void->Void):Void { + super(X, Y, OnClick); + + for (point in labelOffsets) + point.set(point.x - 1, point.y + 3); + } + + /** + * Updates the size of the text field to match the button. + */ + override function resetHelpers():Void { + super.resetHelpers(); + } + + inline function get_text():String { + return null; + } + + inline function set_text(Text:String):String { + return Text; + } +} + +/** + * A simple button class that calls a function when clicked by the touch. + */ +#if !display +@:generic +#end +class FlxTypedButton extends FlxSprite implements IFlxInput +{ + /** + * The label that appears on the button. Can be any `FlxSprite`. + */ + public var label(default, set):T; + + /** + * What offsets the `label` should have for each status. + */ + public var labelOffsets:Array = [FlxPoint.get(), FlxPoint.get(), FlxPoint.get(0, 1)]; + + /** + * What alpha value the label should have for each status. Default is `[0.8, 1.0, 0.5]`. + * Multiplied with the button's `alpha`. + */ + public var labelAlphas:Array = [0.8, 1.0, 0.5]; + + /** + * What animation should be played for each status. + * Default is ['normal', 'highlight', 'pressed']. + */ + public var statusAnimations:Array = ['normal', 'highlight', 'pressed']; + + /** + * How much to add/substract from the current indicator value for the label. + **/ + public var labelStatusDiff:Float = 0.05; + + /** + * Whether you can press the button simply by releasing the touch button over it (default). + * If false, the input has to be pressed while hovering over the button. + */ + public var allowSwiping:Bool = true; + + /** + * Whether the button can use multiple fingers on it. + */ + public var multiTouch:Bool = false; + + /** + * Maximum distance a pointer can move to still trigger event handlers. + * If it moves beyond this limit, onOut is triggered. + * Defaults to `Math.POSITIVE_INFINITY` (i.e. no limit). + */ + public var maxInputMovement:Float = Math.POSITIVE_INFINITY; + + /** + * Shows the current state of the button, either `FlxButton.NORMAL`, + * `FlxButton.HIGHLIGHT` or `FlxButton.PRESSED`. + */ + public var status(default, set):Int; + + /** + * The properties of this button's `onUp` event (callback function, sound). + */ + public var onUp(default, null):FlxButtonEvent; + + /** + * The properties of this button's `onDown` event (callback function, sound). + */ + public var onDown(default, null):FlxButtonEvent; + + /** + * The properties of this button's `onOver` event (callback function, sound). + */ + public var onOver(default, null):FlxButtonEvent; + + /** + * The properties of this button's `onOut` event (callback function, sound). + */ + public var onOut(default, null):FlxButtonEvent; + + public var justReleased(get, never):Bool; + public var released(get, never):Bool; + public var pressed(get, never):Bool; + public var justPressed(get, never):Bool; + + /** + * We cast label to a `FlxSprite` for internal operations to avoid Dynamic casts in C++ + */ + public var _spriteLabel:FlxSprite; + + /** + * We don't need an ID here, so let's just use `Int` as the type. + */ + var input:FlxInput; + + /** + * The input currently pressing this button, if none, it's `null`. Needed to check for its release. + */ + var currentInput:IFlxInput; + + var lastStatus = -1; + + public var canChangeLabelAlpha:Bool = true; + + /** + * Creates a new `FlxTypedButton` object with a gray background. + * + * @param X The x position of the button. + * @param Y The y position of the button. + * @param OnClick The function to call whenever the button is clicked. + */ + public function new(X:Float = 0, Y:Float = 0, ?OnClick:Void->Void):Void { + super(X, Y); + + loadDefaultGraphic(); + + onUp = new FlxButtonEvent(OnClick); + onDown = new FlxButtonEvent(); + onOver = new FlxButtonEvent(); + onOut = new FlxButtonEvent(); + + status = multiTouch ? FlxButton.NORMAL : FlxButton.HIGHLIGHT; + + // Since this is a UI element, the default scrollFactor is (0, 0) + scrollFactor.set(); + + statusAnimations[FlxButton.HIGHLIGHT] = 'normal'; + labelAlphas[FlxButton.HIGHLIGHT] = 1; + + input = new FlxInput(0); + } + + override public function graphicLoaded():Void { + super.graphicLoaded(); + + setupAnimation('normal', FlxButton.NORMAL); + setupAnimation('pressed', FlxButton.PRESSED); + } + + function loadDefaultGraphic():Void + loadGraphic('flixel/images/ui/button.png', true, 80, 20); + + function setupAnimation(animationName:String, frameIndex:Int):Void { + // make sure the animation doesn't contain an invalid frame + frameIndex = Std.int(Math.min(frameIndex, animation.numFrames - 1)); + animation.add(animationName, [frameIndex]); + } + + /** + * Called by the game state when state is changed (if this object belongs to the state) + */ + override public function destroy():Void { + label = FlxDestroyUtil.destroy(label); + _spriteLabel = null; + + onUp = FlxDestroyUtil.destroy(onUp); + onDown = FlxDestroyUtil.destroy(onDown); + onOver = FlxDestroyUtil.destroy(onOver); + onOut = FlxDestroyUtil.destroy(onOut); + + labelOffsets = FlxDestroyUtil.putArray(labelOffsets); + + labelAlphas = null; + currentInput = null; + input = null; + + super.destroy(); + } + + /** + * Called by the game loop automatically, handles touch over and click detection. + */ + override public function update(elapsed:Float):Void { + super.update(elapsed); + + if (visible) { + // Update the button, but only if at least either touches are enabled + #if FLX_POINTER_INPUT + updateButton(); + #end + + // Trigger the animation only if the button's input status changes. + if (lastStatus != status) { + updateStatusAnimation(); + lastStatus = status; + } + } + + input.update(); + } + + function updateStatusAnimation():Void + animation.play(statusAnimations[status]); + + /** + * Just draws the button graphic and text label to the screen. + */ + override public function draw():Void { + super.draw(); + + if (_spriteLabel != null && _spriteLabel.visible) { + _spriteLabel.cameras = cameras; + _spriteLabel.draw(); + } + } + + #if FLX_DEBUG + /** + * Helper function to draw the debug graphic for the label as well. + */ + override public function drawDebug():Void { + super.drawDebug(); + + if (_spriteLabel != null) + _spriteLabel.drawDebug(); + } + #end + + /** + * Stamps button's graphic and label onto specified atlas object and loads graphic from this atlas. + * This method assumes that you're using whole image for button's graphic and image has no spaces between frames. + * And it assumes that label is a single frame sprite. + * + * @param atlas Atlas to stamp graphic to. + * @return Whether the button's graphic and label's graphic were stamped on the atlas successfully. + */ + public function stampOnAtlas(atlas:FlxAtlas):Bool { + var buttonNode:FlxNode = atlas.addNode(graphic.bitmap, graphic.key); + var result:Bool = (buttonNode != null); + + if (buttonNode != null) { + var buttonFrames:FlxTileFrames = cast frames; + var tileSize:FlxPoint = FlxPoint.get(buttonFrames.tileSize.x, buttonFrames.tileSize.y); + var tileFrames:FlxTileFrames = buttonNode.getTileFrames(tileSize); + this.frames = tileFrames; + } + + if (result && label != null) { + var labelNode:FlxNode = atlas.addNode(label.graphic.bitmap, label.graphic.key); + result = result && (labelNode != null); + + if (labelNode != null) + label.frames = labelNode.getImageFrame(); + } + + return result; + } + + /** + * Basic button update logic - searches for overlaps with touches and + * the touch and calls `updateStatus()`. + */ + function updateButton():Void { + var overlapFound = checkTouchOverlap(); + + if (currentInput != null && currentInput.justReleased && overlapFound) + onUpHandler(); + + if (status != FlxButton.NORMAL && (!overlapFound || (currentInput != null && currentInput.justReleased))) + onOutHandler(); + } + + function checkTouchOverlap():Bool { + var overlap = false; + + for (camera in cameras) + for (touch in FlxG.touches.list) + if (checkInput(touch, touch, touch.justPressedPosition, camera)) + overlap = true; + + return overlap; + } + + function checkInput(pointer:FlxPointer, input:IFlxInput, justPressedPosition:FlxPoint, camera:FlxCamera):Bool { + if (maxInputMovement != Math.POSITIVE_INFINITY + && justPressedPosition.distanceTo(pointer.getScreenPosition(FlxPoint.weak())) > maxInputMovement + && input == currentInput) { + currentInput = null; + } else if (overlapsPoint(pointer.getWorldPosition(camera, _point), true, camera)) { + updateStatus(input); + return true; + } + + return false; + } + + /** + * Updates the button status by calling the respective event handler function. + */ + function updateStatus(input:IFlxInput):Void { + if (input.justPressed) { + currentInput = input; + onDownHandler(); + } else if (status == FlxButton.NORMAL) { + // Allow 'swiping' to press a button (dragging it over the button while pressed) + if (allowSwiping && input.pressed) + onDownHandler(); + else + onOverHandler(); + } + } + + function updateLabelPosition() { + if (_spriteLabel != null) // Label positioning + { + _spriteLabel.x = (pixelPerfectPosition ? Math.floor(x) : x) + labelOffsets[status].x; + _spriteLabel.y = (pixelPerfectPosition ? Math.floor(y) : y) + labelOffsets[status].y; + } + } + + function updateLabelAlpha() { + if (_spriteLabel != null && canChangeLabelAlpha) + _spriteLabel.alpha = alpha == 0 ? 0 : alpha + labelStatusDiff; + } + + /** + * Internal function that handles the onUp event. + */ + function onUpHandler():Void { + status = FlxButton.NORMAL; + input.release(); + currentInput = null; + onUp.fire(); // Order matters here, because onUp.fire() could cause a state change and destroy this object. + } + + /** + * Internal function that handles the onDown event. + */ + function onDownHandler():Void { + status = FlxButton.PRESSED; + input.press(); + onDown.fire(); // Order matters here, because onDown.fire() could cause a state change and destroy this object. + } + + /** + * Internal function that handles the onOver event. + */ + function onOverHandler():Void { + status = FlxButton.HIGHLIGHT; + onOver.fire(); // Order matters here, because onOver.fire() could cause a state change and destroy this object. + } + + /** + * Internal function that handles the onOut event. + */ + function onOutHandler():Void { + status = FlxButton.NORMAL; + input.release(); + onOut.fire(); // Order matters here, because onOut.fire() could cause a state change and destroy this object. + } + + function set_label(Value:T):T { + if (Value != null) { + // use the same FlxPoint object for both + Value.scrollFactor.put(); + Value.scrollFactor = scrollFactor; + } + + label = Value; + _spriteLabel = label; + + updateLabelPosition(); + + return Value; + } + + function set_status(Value:Int):Int { + status = Value; + updateLabelAlpha(); + return status; + } + + override function set_alpha(Value:Float):Float { + super.set_alpha(Value); + updateLabelAlpha(); + return alpha; + } + + override function set_x(Value:Float):Float { + super.set_x(Value); + updateLabelPosition(); + return x; + } + + override function set_y(Value:Float):Float { + super.set_y(Value); + updateLabelPosition(); + return y; + } + + inline function get_justReleased():Bool + return input.justReleased; + + inline function get_released():Bool + return input.released; + + inline function get_pressed():Bool + return input.pressed; + + inline function get_justPressed():Bool + return input.justPressed; +} + +/** + * Helper function for `FlxButton` which handles its events. + */ +private class FlxButtonEvent implements IFlxDestroyable +{ + /** + * The callback function to call when this even fires. + */ + public var callback:Void->Void; + + #if FLX_SOUND_SYSTEM + /** + * The sound to play when this event fires. + */ + public var sound:FlxSound; + #end + + /** + * @param Callback The callback function to call when this even fires. + * @param sound The sound to play when this event fires. + */ + public function new(?Callback:Void->Void, ?sound:FlxSound):Void { + callback = Callback; + + #if FLX_SOUND_SYSTEM + this.sound = sound; + #end + } + + /** + * Cleans up memory. + */ + public inline function destroy():Void { + callback = null; + + #if FLX_SOUND_SYSTEM + sound = FlxDestroyUtil.destroy(sound); + #end + } + + /** + * Fires this event (calls the callback and plays the sound) + */ + public inline function fire():Void { + if (callback != null) + callback(); + + #if FLX_SOUND_SYSTEM + if (sound != null) + sound.play(true); + #end + } +} diff --git a/source/mobile/flixel/FlxVirtualPad.hx b/source/mobile/flixel/FlxVirtualPad.hx new file mode 100644 index 000000000..3a4fea9d4 --- /dev/null +++ b/source/mobile/flixel/FlxVirtualPad.hx @@ -0,0 +1,260 @@ +package mobile.flixel; + +import flixel.FlxG; +#if MOD_SUPPORT +import sys.FileSystem; +#end +import flixel.math.FlxPoint; +import funkin.options.Options; +import mobile.flixel.FlxButton; +import openfl.display.BitmapData; +import flixel.util.FlxDestroyUtil; +import flixel.graphics.FlxGraphic; +import funkin.backend.assets.Paths; +import mobile.objects.FlxButtonGroup; +import flixel.graphics.frames.FlxTileFrames; +import flixel.graphics.frames.FlxAtlasFrames; +import openfl.utils.Assets; +import haxe.ds.Map; +import flixel.util.typeLimit.OneOfTwo; + +enum FlxDPadMode +{ + UP_DOWN; + LEFT_RIGHT; + LEFT_FULL; + RIGHT_FULL; + NONE; +} + +enum FlxActionMode +{ + A; + B; + P; + A_B; + A_B_C; + A_B_E; + A_B_X_Y; + A_B_M_E; + A_B_C_X_Y; + A_B_C_X_Y_Z; + A_B_C_D_V_X_Y_Z; + NONE; +} + +/** + * A highly modified FlxVirtualPad. + * It's easy to customize the layout. + * + * @author Ka Wing Chin + * @author Mihai Alexandru (M.A. Jigsaw) + */ +class FlxVirtualPad extends FlxButtonGroup +{ + public var buttonLeft:FlxButton = new FlxButton(0, 0); + public var buttonUp:FlxButton = new FlxButton(0, 0); + public var buttonRight:FlxButton = new FlxButton(0, 0); + public var buttonDown:FlxButton = new FlxButton(0, 0); + public var buttonLeft2:FlxButton = new FlxButton(0, 0); + public var buttonUp2:FlxButton = new FlxButton(0, 0); + public var buttonRight2:FlxButton = new FlxButton(0, 0); + public var buttonDown2:FlxButton = new FlxButton(0, 0); + public var buttonA:FlxButton = new FlxButton(0, 0); + public var buttonB:FlxButton = new FlxButton(0, 0); + public var buttonC:FlxButton = new FlxButton(0, 0); + public var buttonD:FlxButton = new FlxButton(0, 0); + public var buttonE:FlxButton = new FlxButton(0, 0); + public var buttonF:FlxButton = new FlxButton(0, 0); + public var buttonG:FlxButton = new FlxButton(0, 0); + public var buttonH:FlxButton = new FlxButton(0, 0); + public var buttonI:FlxButton = new FlxButton(0, 0); + public var buttonJ:FlxButton = new FlxButton(0, 0); + public var buttonK:FlxButton = new FlxButton(0, 0); + public var buttonL:FlxButton = new FlxButton(0, 0); + public var buttonM:FlxButton = new FlxButton(0, 0); + public var buttonN:FlxButton = new FlxButton(0, 0); + public var buttonO:FlxButton = new FlxButton(0, 0); + public var buttonP:FlxButton = new FlxButton(0, 0); + public var buttonQ:FlxButton = new FlxButton(0, 0); + public var buttonR:FlxButton = new FlxButton(0, 0); + public var buttonS:FlxButton = new FlxButton(0, 0); + public var buttonT:FlxButton = new FlxButton(0, 0); + public var buttonU:FlxButton = new FlxButton(0, 0); + public var buttonV:FlxButton = new FlxButton(0, 0); + public var buttonW:FlxButton = new FlxButton(0, 0); + public var buttonX:FlxButton = new FlxButton(0, 0); + public var buttonY:FlxButton = new FlxButton(0, 0); + public var buttonZ:FlxButton = new FlxButton(0, 0); + + public var curDPadMode:FlxDPadMode = NONE; + public var curActionMode:FlxActionMode = NONE; + public static var dpadModes:Map; + public static var actionModes:Map; + + /** + * Create a gamepad. + * + * @param FlxDPadMode The D-Pad mode. `LEFT_FULL` for example. + * @param FlxActionMode The action buttons mode. `A_B_C` for example. + */ + public function new(DPad:OneOfTwo, Action:OneOfTwo) + { + super(); + var dpadMode:FlxDPadMode; + var actionMode:FlxActionMode; + + if(DPad is FlxDPadMode) + dpadMode = cast DPad; + else + dpadMode = cast getDPadModeByString(cast DPad); + + if(Action is FlxActionMode) + actionMode = cast DPad; + else + actionMode = cast getActionModeByString(cast Action); + curDPadMode = dpadMode; + curActionMode = actionMode; + switch (dpadMode) + { + case UP_DOWN: + add(buttonUp = createButton(0, FlxG.height - 258, 'up', 0x00FF00)); + add(buttonDown = createButton(0, FlxG.height - 131, 'down', 0x00FFFF)); + case LEFT_RIGHT: + add(buttonLeft = createButton(0, FlxG.height - 131, 'left', 0xFF00FF)); + add(buttonRight = createButton(127, FlxG.height - 131, 'right', 0xFF0000)); + case LEFT_FULL: + add(buttonUp = createButton(105, FlxG.height - 356, 'up', 0x00FF00)); + add(buttonLeft = createButton(0, FlxG.height - 246, 'left', 0xFF00FF)); + add(buttonRight = createButton(207, FlxG.height - 246, 'right', 0xFF0000)); + add(buttonDown = createButton(105, FlxG.height - 131, 'down', 0x00FFFF)); + case RIGHT_FULL: + add(buttonUp = createButton(FlxG.width - 258, FlxG.height - 404, 'up', 0x00FF00)); + add(buttonLeft = createButton(FlxG.width - 384, FlxG.height - 305, 'left', 0xFF00FF)); + add(buttonRight = createButton(FlxG.width - 132, FlxG.height - 305, 'right', 0xFF0000)); + add(buttonDown = createButton(FlxG.width - 258, FlxG.height - 197, 'down', 0x00FFFF)); + case NONE: // do nothing + } + + switch (actionMode) + { + case A: + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case B: + add(buttonB = createButton(FlxG.width - 132, FlxG.height - 131, 'b', 0xFFCB00)); + case P: + add(buttonP = createButton(FlxG.width - 132, 0, 'p', 0xFFCB00)); + case A_B: + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_C: + add(buttonC = createButton(FlxG.width - 392, FlxG.height - 131, 'c', 0x44FF00)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_E: + add(buttonE = createButton(FlxG.width - 392, FlxG.height - 131, 'e', 0xFF7D00)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_X_Y: + add(buttonX = createButton(FlxG.width - 522, FlxG.height - 131, 'x', 0x99062D)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonY = createButton(FlxG.width - 392, FlxG.height - 131, 'y', 0x4A35B9)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_C_X_Y: + add(buttonC = createButton(FlxG.width - 392, FlxG.height - 131, 'c', 0x44FF00)); + add(buttonX = createButton(FlxG.width - 262, FlxG.height - 251, 'x', 0x99062D)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonY = createButton(FlxG.width - 132, FlxG.height - 251, 'y', 0x4A35B9)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_C_X_Y_Z: + add(buttonX = createButton(FlxG.width - 392, FlxG.height - 251, 'x', 0x99062D)); + add(buttonC = createButton(FlxG.width - 392, FlxG.height - 131, 'c', 0x44FF00)); + add(buttonY = createButton(FlxG.width - 262, FlxG.height - 251, 'y', 0x4A35B9)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonZ = createButton(FlxG.width - 132, FlxG.height - 251, 'z', 0xCCB98E)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case A_B_C_D_V_X_Y_Z: + add(buttonV = createButton(FlxG.width - 522, FlxG.height - 251, 'v', 0x49A9B2)); + add(buttonD = createButton(FlxG.width - 522, FlxG.height - 131, 'd', 0x0078FF)); + add(buttonX = createButton(FlxG.width - 392, FlxG.height - 251, 'x', 0x99062D)); + add(buttonC = createButton(FlxG.width - 392, FlxG.height - 131, 'c', 0x44FF00)); + add(buttonY = createButton(FlxG.width - 262, FlxG.height - 251, 'y', 0x4A35B9)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonZ = createButton(FlxG.width - 132, FlxG.height - 251, 'z', 0xCCB98E)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + // CNE Releated + case A_B_M_E: + add(buttonM = createButton(FlxG.width - 522, FlxG.height - 131, 'm', 0x00BBFF)); + add(buttonB = createButton(FlxG.width - 262, FlxG.height - 131, 'b', 0xFFCB00)); + add(buttonE = createButton(FlxG.width - 392, FlxG.height - 131, 'e', 0xFF7D00)); + add(buttonA = createButton(FlxG.width - 132, FlxG.height - 131, 'a', 0xFF0000)); + case NONE: // do nothing + } + + scrollFactor.set(); + var guh = Options.controlsAlpha; + if (guh >= 0.9) + guh = guh - 0.07; + alpha = Options.controlsAlpha; + } + + public static function getDPadModeByString(mode:String):FlxDPadMode { + if(dpadModes == null){ + dpadModes = new Map(); + for(enumValue in FlxDPadMode.createAll()) + dpadModes.set(enumValue.getName(), enumValue); + } + return dpadModes.exists(mode) ? dpadModes.get(mode) : NONE; + } + + public static function getActionModeByString(mode:String):FlxActionMode { + if(actionModes == null){ + actionModes = new Map(); + for(enumValue in FlxActionMode.createAll()) + actionModes.set(enumValue.getName(), enumValue); + } + return actionModes.exists(mode) ? actionModes.get(mode) : NONE; + } + + /** + * Clean up memory. + */ + override public function destroy():Void + { + super.destroy(); + + for (field in Reflect.fields(this)) + if (Std.isOfType(Reflect.field(this, field), FlxButton)) + Reflect.setField(this, field, FlxDestroyUtil.destroy(Reflect.field(this, field))); + } + + private function createButton(X:Float, Y:Float, Graphic:String, Color:Int = 0xFFFFFF):FlxButton + { + var graphic:FlxGraphic; + var path:String = Paths.image('mobile/virtualpad/$Graphic'); + #if MOD_SUPPORT + if(FileSystem.exists(path)) + graphic = FlxGraphic.fromBitmapData(BitmapData.fromFile(path)); + else #end if(Assets.exists(path)) + graphic = FlxGraphic.fromBitmapData(Assets.getBitmapData(path)); + else + graphic = FlxGraphic.fromBitmapData(Assets.getBitmapData(Paths.image('mobile/virtualpad/default'))); + + var button:FlxButton = new FlxButton(X, Y); + try { + button.frames = FlxTileFrames.fromGraphic(graphic, FlxPoint.get(Std.int(graphic.width / 2), graphic.height)); + } + catch (e){ + trace("Failed to create button(s) " + e.message); + return null; + } + button.solid = false; + button.immovable = true; + button.scrollFactor.set(); + button.color = Color; + #if FLX_DEBUG + button.ignoreDrawDebug = true; + #end + return button; + } +} \ No newline at end of file diff --git a/source/mobile/funkin/backend/system/CopyState.hx b/source/mobile/funkin/backend/system/CopyState.hx new file mode 100644 index 000000000..b9d8e1a0a --- /dev/null +++ b/source/mobile/funkin/backend/system/CopyState.hx @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2024 Mobile Porting Team + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package mobile.funkin.backend.system; + +#if mobile +import lime.utils.Assets as LimeAssets; +import openfl.utils.Assets as OpenFLAssets; +import flixel.addons.util.FlxAsyncLoop; +import flixel.FlxG; +import flixel.text.FlxText; +import flixel.FlxSprite; +import flixel.util.FlxColor; +import openfl.utils.ByteArray; +import haxe.io.Path; +import mobile.funkin.backend.utils.MobileUtil; +import funkin.backend.assets.Paths; +import funkin.backend.utils.NativeAPI; +import flixel.ui.FlxBar; +import flixel.ui.FlxBar.FlxBarFillDirection; + +#if sys +import sys.io.File; +import sys.FileSystem; +#end + +using StringTools; + +/** + * ... + * @author: Karim Akra + */ +class CopyState extends funkin.backend.MusicBeatState +{ + private static final textFilesExtensions:Array = ['ini', 'txt', 'xml', 'hxs', 'hx', 'lua', 'json', 'frag', 'vert']; + public static final IGNORE_FOLDER_FILE_NAME:String = "CopyState-Ignore.txt"; + private static var directoriesToIgnore:Array = []; + public static var locatedFiles:Array = []; + public static var maxLoopTimes:Int = 0; + + public var loadingImage:FlxSprite; + public var loadingBar:FlxBar; + public var loadedText:FlxText; + public var copyLoop:FlxAsyncLoop; + + var failedFilesStack:Array = []; + var failedFiles:Array = []; + var shouldCopy:Bool = false; + var canUpdate:Bool = true; + var loopTimes:Int = 0; + + override function create() + { + locatedFiles = []; + maxLoopTimes = 0; + checkExistingFiles(); + if (maxLoopTimes <= 0) + { + FlxG.resetGame(); + return; + } + + NativeAPI.showMessageBox("Notice", "Seems like you have some missing files that are necessary to run the game\nPress OK to begin the copy process"); + + shouldCopy = true; + + add(new FlxSprite(0, 0).makeGraphic(FlxG.width, FlxG.height, 0xffcaff4d)); + + loadingImage = new FlxSprite(0, 0, Paths.image('menus/funkay')); + loadingImage.setGraphicSize(0, FlxG.height); + loadingImage.updateHitbox(); + loadingImage.screenCenter(); + add(loadingImage); + + loadingBar = new FlxBar(0, FlxG.height - 26, FlxBarFillDirection.LEFT_TO_RIGHT, FlxG.width, 26); + loadingBar.setRange(0, maxLoopTimes); + add(loadingBar); + + loadedText = new FlxText(loadingBar.x, loadingBar.y + 4, FlxG.width, '', 16); + loadedText.setFormat(Paths.font("vcr.ttf"), 16, FlxColor.WHITE, CENTER); + add(loadedText); + + var ticks:Int = 15; + if (maxLoopTimes <= 15) + ticks = 1; + + copyLoop = new FlxAsyncLoop(maxLoopTimes, copyAsset, ticks); + add(copyLoop); + copyLoop.start(); + + super.create(); + } + + override function update(elapsed:Float) + { + if (shouldCopy && copyLoop != null) + { + loadingBar.percent = loopTimes / maxLoopTimes * 100; + if (copyLoop.finished && canUpdate) + { + if (failedFiles.length > 0) + { + NativeAPI.showMessageBox('Failed To Copy ${failedFiles.length} File.', failedFiles.join('\n'), MSG_ERROR); + if (!FileSystem.exists('logs')) + FileSystem.createDirectory('logs'); + File.saveContent('logs/' + Date.now().toString().replace(' ', '-').replace(':', "'") + '-CopyState' + '.txt', failedFilesStack.join('\n')); + } + canUpdate = false; + FlxG.sound.play(Paths.sound('menu/confirm')).onComplete = () -> + { + FlxG.resetGame(); + }; + } + + if (loopTimes == maxLoopTimes) + loadedText.text = "Completed!"; + else + loadedText.text = '$loopTimes/$maxLoopTimes'; + } + super.update(elapsed); + } + + public function copyAsset() + { + var file = locatedFiles[loopTimes]; + loopTimes++; + if (!FileSystem.exists(file)) + { + var directory = Path.directory(file); + if (!FileSystem.exists(directory)) + FileSystem.createDirectory(directory); + try + { + if (OpenFLAssets.exists(getFile(file))) + { + if (textFilesExtensions.contains(Path.extension(file))) + createContentFromInternal(file); + else + File.saveBytes(file, getFileBytes(getFile(file))); + } + else + { + failedFiles.push(getFile(file) + " (File Dosen't Exist)"); + failedFilesStack.push('Asset ${getFile(file)} does not exist.'); + } + } + catch (e:haxe.Exception) + { + failedFiles.push('${getFile(file)} (${e.message})'); + failedFilesStack.push('${getFile(file)} (${e.stack})'); + } + } + } + + public function createContentFromInternal(file:String) + { + var fileName = Path.withoutDirectory(file); + var directory = Path.directory(file); + try + { + var fileData:String = OpenFLAssets.getText(getFile(file)); + if (fileData == null) + fileData = ''; + if (!FileSystem.exists(directory)) + FileSystem.createDirectory(directory); + File.saveContent(Path.join([directory, fileName]), fileData); + } + catch (e:haxe.Exception) + { + failedFiles.push('${getFile(file)} (${e.message})'); + failedFilesStack.push('${getFile(file)} (${e.stack})'); + } + } + + public function getFileBytes(file:String):ByteArray + { + switch (Path.extension(file).toLowerCase()) + { + case 'otf' | 'ttf': + return ByteArray.fromFile(file); + default: + return OpenFLAssets.getBytes(file); + } + } + + public static function getFile(file:String):String + { + if (OpenFLAssets.exists(file)) + return file; + + @:privateAccess + for (library in LimeAssets.libraries.keys()) + { + if (OpenFLAssets.exists('$library:$file') && library != 'default') + return '$library:$file'; + } + + return file; + } + + public static function checkExistingFiles():Bool + { + locatedFiles = Paths.assetsTree.list(null); + + // removes unwanted assets + var assets = locatedFiles.filter(folder -> folder.startsWith('assets/')); + var mods = locatedFiles.filter(folder -> folder.startsWith('mods/')); + locatedFiles = assets.concat(mods); + locatedFiles = locatedFiles.filter(file -> !FileSystem.exists(file)); + + var filesToRemove:Array = []; + + for (file in locatedFiles) + { + if (filesToRemove.contains(file)) + continue; + + if (file.endsWith(IGNORE_FOLDER_FILE_NAME) && !directoriesToIgnore.contains(Path.directory(file))) + directoriesToIgnore.push(Path.directory(file)); + + if (directoriesToIgnore.length > 0) + { + for (directory in directoriesToIgnore) + { + if (file.startsWith(directory)) + filesToRemove.push(file); + } + } + } + + locatedFiles = locatedFiles.filter(file -> !filesToRemove.contains(file)); + + maxLoopTimes = locatedFiles.length; + + return (maxLoopTimes <= 0); + } +} +#end \ No newline at end of file diff --git a/source/mobile/funkin/backend/utils/MobileUtil.hx b/source/mobile/funkin/backend/utils/MobileUtil.hx new file mode 100644 index 000000000..7b62e8fc2 --- /dev/null +++ b/source/mobile/funkin/backend/utils/MobileUtil.hx @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 Mobile Porting Team + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package mobile.funkin.backend.utils; + +#if android +import android.content.Context; +import android.os.Environment; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.Permissions; +import android.Settings; +#end +import lime.system.System as LimeSystem; +import funkin.backend.utils.NativeAPI; +#if sys +import sys.io.File; +import sys.FileSystem; +#end + +using StringTools; + +/** + * A storage class for mobile. + * @author Lily Ross (mcagabe19) + */ +class MobileUtil +{ + #if sys + public static function getStorageDirectory(?force:Bool = false):String + { + var daPath:String; + + #if android + if (!FileSystem.exists(LimeSystem.applicationStorageDirectory + 'storagetype.txt')) + File.saveContent(LimeSystem.applicationStorageDirectory + 'storagetype.txt', funkin.options.Options.storageType); + var curStorageType:String = File.getContent(LimeSystem.applicationStorageDirectory + 'storagetype.txt'); + daPath = force ? StorageType.fromStrForce(curStorageType) : StorageType.fromStr(curStorageType); + daPath = haxe.io.Path.addTrailingSlash(daPath); + #elseif ios + daPath = LimeSystem.documentsDirectory; + #else + daPath = Sys.getCwd(); + #end + + return daPath; + } + + #if android + public static function requestPermissionsFromUser():Void + { + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) + Permissions.requestPermissions(['READ_MEDIA_IMAGES', 'READ_MEDIA_VIDEO', 'READ_MEDIA_AUDIO']); + else + Permissions.requestPermissions(['READ_EXTERNAL_STORAGE', 'WRITE_EXTERNAL_STORAGE']); + + if (!Environment.isExternalStorageManager()) + { + if (VERSION.SDK_INT >= VERSION_CODES.S) + Settings.requestSetting('REQUEST_MANAGE_MEDIA'); + Settings.requestSetting('MANAGE_APP_ALL_FILES_ACCESS_PERMISSION'); + } + + if ((VERSION.SDK_INT >= VERSION_CODES.TIRAMISU && !Permissions.getGrantedPermissions().contains('android.permission.READ_MEDIA_IMAGES')) || (VERSION.SDK_INT < VERSION_CODES.TIRAMISU && !Permissions.getGrantedPermissions().contains('android.permission.READ_EXTERNAL_STORAGE'))) + NativeAPI.showMessageBox('Notice!', 'If you accepted the permissions you are all good!' + '\nIf you didn\'t then expect a crash' + '\nPress Ok to see what happens', MSG_INFORMATION); + + try + { + if (!FileSystem.exists(MobileUtil.getStorageDirectory())) + FileSystem.createDirectory(MobileUtil.getStorageDirectory()); + } + catch (e:Dynamic) + { + NativeAPI.showMessageBox('Error!', 'Please create folder to\n' + MobileUtil.getStorageDirectory(true) + '\nPress OK to close the game', MSG_ERROR); + LimeSystem.exit(1); + } + } + + public static function checkExternalPaths(?splitStorage = false):Array { + var process = new funkin.backend.utils.native.HiddenProcess('grep -o "/storage/....-...." /proc/mounts | paste -sd \',\''); + var paths:String = process.stdout.readAll().toString(); + if (splitStorage) paths = paths.replace('/storage/', ''); + return paths.split(','); + } + + public static function getExternalDirectory(external:String):String { + var daPath:String = ''; + for (path in checkExternalPaths()) + if (path.contains(external)) daPath = path; + + daPath = haxe.io.Path.addTrailingSlash(daPath.endsWith("\n") ? daPath.substr(0, daPath.length - 1) : daPath); + return daPath; + } + #end + #end +} + +#if android +enum abstract StorageType(String) from String to String +{ + final forcedPath = '/storage/emulated/0/'; + final packageNameLocal = 'com.yoshman29.codenameengine'; + final fileLocal = 'CodenameEngine'; + + public static function fromStr(str:String):StorageType + { + final EXTERNAL_DATA = Context.getExternalFilesDir(); + final EXTERNAL_OBB = Context.getObbDir(); + final EXTERNAL_MEDIA = Environment.getExternalStorageDirectory() + '/Android/media/' + lime.app.Application.current.meta.get('packageName'); + final EXTERNAL = Environment.getExternalStorageDirectory() + '/.' + lime.app.Application.current.meta.get('file'); + + return switch (str) + { + case "EXTERNAL_DATA": EXTERNAL_DATA; + case "EXTERNAL_OBB": EXTERNAL_OBB; + case "EXTERNAL_MEDIA": EXTERNAL_MEDIA; + case "EXTERNAL": EXTERNAL; + default: MobileUtil.getExternalDirectory(str) + '.' + fileLocal; + } + } + + public static function fromStrForce(str:String):StorageType + { + final EXTERNAL_DATA = forcedPath + 'Android/data/' + packageNameLocal + '/files'; + final EXTERNAL_OBB = forcedPath + 'Android/obb/' + packageNameLocal; + final EXTERNAL_MEDIA = forcedPath + 'Android/media/' + packageNameLocal; + final EXTERNAL = forcedPath + '.' + fileLocal; + + return switch (str) + { + case "EXTERNAL_DATA": EXTERNAL_DATA; + case "EXTERNAL_OBB": EXTERNAL_OBB; + case "EXTERNAL_MEDIA": EXTERNAL_MEDIA; + case "EXTERNAL": EXTERNAL; + default: MobileUtil.getExternalDirectory(str) + '.' + fileLocal; + } + } +} +#end diff --git a/source/mobile/funkin/backend/utils/TouchUtil.hx b/source/mobile/funkin/backend/utils/TouchUtil.hx new file mode 100644 index 000000000..5b818af5c --- /dev/null +++ b/source/mobile/funkin/backend/utils/TouchUtil.hx @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 Mobile Porting Team + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package mobile.funkin.backend.utils; + +import flixel.FlxG; +import flixel.FlxCamera; +import flixel.FlxObject; +import flixel.input.touch.FlxTouch; + +/** + * ... + * @author: Karim Akra + */ +class TouchUtil +{ + public static var pressed(get, never):Bool; + public static var justPressed(get, never):Bool; + public static var justReleased(get, never):Bool; + public static var released(get, never):Bool; + public static var touch(get, never):FlxTouch; + + public static function overlaps(object:FlxObject, ?camera:FlxCamera):Bool + { + var cam = (camera != null) ? camera : object.camera; + for (touch in FlxG.touches.list) + if (touch.overlaps(object, cam)) + return true; + + return false; + } + + public static function overlapsComplex(object:FlxObject, ?camera:FlxCamera):Bool + { + if (camera == null) + for (camera in object.cameras) + for (touch in FlxG.touches.list) + @:privateAccess + if (object.overlapsPoint(touch.getWorldPosition(camera, object._point), true, camera)) + return true; + else + @:privateAccess + if (object.overlapsPoint(touch.getWorldPosition(camera, object._point), true, camera)) + return true; + + return false; + } + + @:noCompletion + private static function get_pressed():Bool + { + for (touch in FlxG.touches.list) + if (touch.pressed) + return true; + + return false; + } + + @:noCompletion + private static function get_justPressed():Bool + { + for (touch in FlxG.touches.list) + if (touch.justPressed) + return true; + + return false; + } + + @:noCompletion + private static function get_justReleased():Bool + { + for (touch in FlxG.touches.list) + if (touch.justReleased) + return true; + + return false; + } + + @:noCompletion + private static function get_released():Bool + { + for (touch in FlxG.touches.list) + if (touch.released) + return true; + + return false; + } + + @:noCompletion + private static function get_touch():FlxTouch + { + for (touch in FlxG.touches.list) + if (touch != null) + return touch; + + return FlxG.touches.getFirst(); + } +} \ No newline at end of file diff --git a/source/mobile/objects/FlxButtonGroup.hx b/source/mobile/objects/FlxButtonGroup.hx new file mode 100644 index 000000000..f18dc5c2b --- /dev/null +++ b/source/mobile/objects/FlxButtonGroup.hx @@ -0,0 +1,6 @@ +package mobile.objects; + +import mobile.flixel.FlxButton; +import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; + +typedef FlxButtonGroup = FlxTypedSpriteGroup; \ No newline at end of file diff --git a/source/mobile/objects/Hitbox.hx b/source/mobile/objects/Hitbox.hx new file mode 100644 index 000000000..02a804007 --- /dev/null +++ b/source/mobile/objects/Hitbox.hx @@ -0,0 +1,170 @@ +package mobile.objects; + +import flixel.FlxSprite; +import flixel.FlxG; +import flixel.tweens.*; +import flixel.util.FlxColor; +import openfl.display.Shape; +import funkin.options.Options; +import mobile.flixel.FlxButton; +import openfl.display.BitmapData; +import flixel.util.FlxDestroyUtil; +import openfl.geom.Matrix; + +/** + * A zone with 4 hint's (A hitbox). + * It's really easy to customize the layout. + * + * @author: Mihai Alexandru and Karim Akra + */ +class Hitbox extends FlxButtonGroup +{ + public var buttonLeft:FlxButton = new FlxButton(0, 0); + public var buttonDown:FlxButton = new FlxButton(0, 0); + public var buttonUp:FlxButton = new FlxButton(0, 0); + public var buttonRight:FlxButton = new FlxButton(0, 0); + public var buttonExtra:FlxButton = new FlxButton(0, 0); + public var buttonExtra2:FlxButton = new FlxButton(0, 0); + + /** + * Create the zone. + */ + public function new() + { + super(); + + add(buttonLeft = createHint(0, 0, Std.int(FlxG.width / 4), FlxG.height, 0xFFC24B99)); + add(buttonDown = createHint(FlxG.width / 4, 0, Std.int(FlxG.width / 4), FlxG.height, 0xFF00FFFF)); + add(buttonUp = createHint(FlxG.width / 2, 0, Std.int(FlxG.width / 4), FlxG.height, 0xFF12FA05)); + add(buttonRight = createHint((FlxG.width / 2) + (FlxG.width / 4), 0, Std.int(FlxG.width / 4), FlxG.height, 0xFFF9393F)); + scrollFactor.set(); + var guh = Options.controlsAlpha; + if (guh >= 0.9) + guh = guh - 0.07; + alpha = Options.controlsAlpha; + } + + /** + * Clean up memory. + */ + override function destroy() + { + super.destroy(); + + buttonLeft = FlxDestroyUtil.destroy(buttonLeft); + buttonDown = FlxDestroyUtil.destroy(buttonDown); + buttonUp = FlxDestroyUtil.destroy(buttonUp); + buttonRight = FlxDestroyUtil.destroy(buttonRight); + buttonExtra = FlxDestroyUtil.destroy(buttonExtra); + buttonExtra2 = FlxDestroyUtil.destroy(buttonExtra2); + } + + private function createHint(X:Float, Y:Float, Width:Int, Height:Int, Color:Int = 0xFFFFFF):FlxButton + { + var hintTween:FlxTween = null; + var hintLaneTween:FlxTween = null; + var hint = new FlxButton(X, Y); + hint.loadGraphic(createHintGraphic(Width, Height)); + hint.color = Color; + hint.solid = false; + hint.immovable = true; + hint.multiTouch = true; + hint.moves = false; + hint.scrollFactor.set(); + hint.alpha = 0.00001; + hint.antialiasing = Options.antialiasing; + hint.label = new FlxSprite(); + hint.labelStatusDiff = (Options.hitboxType != "hidden") ? Options.controlsAlpha : 0.00001; + hint.label.loadGraphic(createHintGraphic(Width, Math.floor(Height * 0.035), true)); + hint.label.color = Color; + hint.label.offset.y -= (hint.height - hint.label.height); + if (Options.hitboxType != 'hidden') + { + hint.onDown.callback = function() + { + if (hintTween != null) + hintTween.cancel(); + + if (hintLaneTween != null) + hintLaneTween.cancel(); + + hintTween = FlxTween.tween(hint, {alpha: Options.controlsAlpha}, Options.controlsAlpha / 100, { + ease: FlxEase.circInOut, + onComplete: (twn:FlxTween) -> hintTween = null + }); + + hintLaneTween = FlxTween.tween(hint.label, {alpha: 0.00001}, Options.controlsAlpha / 10, { + ease: FlxEase.circInOut, + onComplete: (twn:FlxTween) -> hintLaneTween = null + }); + } + + hint.onOut.callback = hint.onUp.callback = function() + { + if (hintTween != null) + hintTween.cancel(); + + if (hintLaneTween != null) + hintLaneTween.cancel(); + + hintTween = FlxTween.tween(hint, {alpha: 0.00001}, Options.controlsAlpha / 10, { + ease: FlxEase.circInOut, + onComplete: (twn:FlxTween) -> hintTween = null + }); + + hintLaneTween = FlxTween.tween(hint.label, {alpha: Options.controlsAlpha}, Options.controlsAlpha / 100, { + ease: FlxEase.circInOut, + onComplete: (twn:FlxTween) -> hintLaneTween = null + }); + } + } + #if FLX_DEBUG + hint.ignoreDrawDebug = true; + #end + return hint; + } + + function createHintGraphic(Width:Int, Height:Int, ?isLane:Bool = false):BitmapData + { + var guh = Options.controlsAlpha; + if (guh >= 0.9) + guh = Options.controlsAlpha - 0.07; + var shape:Shape = new Shape(); + shape.graphics.beginFill(0xFFFFFF); + if (Options.hitboxType == "noGradient") + { + var matrix:Matrix = new Matrix(); + matrix.createGradientBox(Width, Height, 0, 0, 0); + + if (isLane) + shape.graphics.beginFill(0xFFFFFF); + else + shape.graphics.beginGradientFill(RADIAL, [0xFFFFFF, 0xFFFFFF], [0, 1], [60, 255], matrix, PAD, RGB, 0); + shape.graphics.drawRect(0, 0, Width, Height); + shape.graphics.endFill(); + } + else if (Options.hitboxType == 'gradient') + { + shape.graphics.lineStyle(3, 0xFFFFFF, 1); + shape.graphics.drawRect(0, 0, Width, Height); + shape.graphics.lineStyle(0, 0, 0); + shape.graphics.drawRect(3, 3, Width - 6, Height - 6); + shape.graphics.endFill(); + if (isLane) + shape.graphics.beginFill(0xFFFFFF); + else + shape.graphics.beginGradientFill(RADIAL, [0xFFFFFF, FlxColor.TRANSPARENT], [1, 0], [0, 255], null, null, null, 0.5); + shape.graphics.drawRect(3, 3, Width - 6, Height - 6); + shape.graphics.endFill(); + } + else //if (Options.hitboxType == "noGradientOld") + { + shape.graphics.lineStyle(10, 0xFFFFFF, 1); + shape.graphics.drawRect(0, 0, Width, Height); + shape.graphics.endFill(); + } + var bitmap:BitmapData = new BitmapData(Width, Height, true, 0); + bitmap.draw(shape); + return bitmap; + } +}