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;
+ }
+}