diff --git a/build.gradle b/build.gradle
index 6f5095a01..ee880790c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,6 +5,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.1'
+ classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.13'
}
}
@@ -24,12 +25,14 @@ allprojects {
appAndroidTargetSDK = 30
gdxVersion = '1.10.0'
+ robovmVersion = '2.3.13'
}
version = appVersionName
repositories {
google()
mavenCentral()
+ maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
}
\ No newline at end of file
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 000000000..cbe6e69bf
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,5 @@
+#RoboVM build folders
+robovm-build/
+
+#RoboVM config (we dynamically generate it)
+robovm.properties
\ No newline at end of file
diff --git a/ios/Info.plist b/ios/Info.plist
new file mode 100644
index 000000000..f3864fa3e
--- /dev/null
+++ b/ios/Info.plist
@@ -0,0 +1,60 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ ${appName}
+ CFBundleExecutable
+ ${appExecutable}
+ CFBundleIdentifier
+ ${appPackageName}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${appName}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ ${appShortVersionName}
+ CFBundleVersionString
+ ${appVersionName}
+ CFBundleVersion
+ ${appVersionCode}
+ LSRequiresIPhoneOS
+
+ UIStatusBarHidden
+
+ UIViewControllerBasedStatusBarAppearance
+
+ UIStatusBarStyle
+ UIStatusBarStyleLightContent
+ MinimumOSVersion
+ 9.0
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ opengles-2
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiresFullScreen
+
+ CFBundleIconName
+ AppIcon
+ ITSAppUsesNonExemptEncryption
+
+
+
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/assets/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..3e00d9b65
--- /dev/null
+++ b/ios/assets/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,166 @@
+{
+ "images" : [
+ {
+ "filename" : "Icon-20@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Icon-20@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Icon-29@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Icon-29@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Icon-40@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Icon-40@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Icon-60@2x.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "Icon-60@3x.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "Icon-20.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Icon-20@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "Icon-29.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Icon-29@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "Icon-40.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Icon-40@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "Icon-76.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "Icon-76@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "Icon-83.5@2x.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "Icon-1024.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "16x16"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "32x32"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "128x128"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "256x256"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "1x",
+ "size" : "512x512"
+ },
+ {
+ "idiom" : "mac",
+ "scale" : "2x",
+ "size" : "512x512"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-1024.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
new file mode 100644
index 000000000..044ec3d72
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-1024.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20.png
new file mode 100644
index 000000000..5dac20f61
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@2x.png
new file mode 100644
index 000000000..80c915be2
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@2x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@3x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@3x.png
new file mode 100644
index 000000000..20dff0c36
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-20@3x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29.png
new file mode 100644
index 000000000..a143de4a5
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png
new file mode 100644
index 000000000..1b4ffef47
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png
new file mode 100644
index 000000000..c1e2714ca
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40.png
new file mode 100644
index 000000000..80c915be2
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png
new file mode 100644
index 000000000..bb9b724af
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png
new file mode 100644
index 000000000..eee38a33d
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
new file mode 100644
index 000000000..eee38a33d
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
new file mode 100644
index 000000000..30c9283b2
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76.png
new file mode 100644
index 000000000..ee9b921b4
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
new file mode 100644
index 000000000..e65089565
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png differ
diff --git a/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
new file mode 100644
index 000000000..570ee3368
Binary files /dev/null and b/ios/assets/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png differ
diff --git a/ios/assets/Assets.xcassets/Banner.imageset/Banner.png b/ios/assets/Assets.xcassets/Banner.imageset/Banner.png
new file mode 100644
index 000000000..63e0102a3
Binary files /dev/null and b/ios/assets/Assets.xcassets/Banner.imageset/Banner.png differ
diff --git a/ios/assets/Assets.xcassets/Banner.imageset/Banner@2x.png b/ios/assets/Assets.xcassets/Banner.imageset/Banner@2x.png
new file mode 100644
index 000000000..c4114d000
Binary files /dev/null and b/ios/assets/Assets.xcassets/Banner.imageset/Banner@2x.png differ
diff --git a/ios/assets/Assets.xcassets/Banner.imageset/Banner@3x.png b/ios/assets/Assets.xcassets/Banner.imageset/Banner@3x.png
new file mode 100644
index 000000000..7bb0efc6f
Binary files /dev/null and b/ios/assets/Assets.xcassets/Banner.imageset/Banner@3x.png differ
diff --git a/ios/assets/Assets.xcassets/Banner.imageset/Contents.json b/ios/assets/Assets.xcassets/Banner.imageset/Contents.json
new file mode 100644
index 000000000..c4af414d2
--- /dev/null
+++ b/ios/assets/Assets.xcassets/Banner.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "Banner.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "Banner@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "Banner@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/ios/assets/LaunchScreen.storyboard b/ios/assets/LaunchScreen.storyboard
new file mode 100644
index 000000000..650bf4296
--- /dev/null
+++ b/ios/assets/LaunchScreen.storyboard
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/build.gradle b/ios/build.gradle
new file mode 100644
index 000000000..fbee1ddb1
--- /dev/null
+++ b/ios/build.gradle
@@ -0,0 +1,43 @@
+apply plugin: "java"
+apply plugin: "robovm"
+
+[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
+sourceCompatibility = targetCompatibility = appJavaCompatibility
+
+task updateRoboVMProps(){
+ def props = new Properties()
+
+ props.setProperty ('appName', appName)
+ //append .apple because com.shatteredpixel.shatteredpixeldungeon was taken =(
+ props.setProperty ('appPackageName', appPackageName + ".apple")
+
+ props.setProperty ('appVersionCode', appVersionCode.toString())
+ props.setProperty ('appVersionName', appVersionName)
+ //parse out just #.#.# from version name, this is an apple requirement
+ props.setProperty ('appShortVersionName', (appVersionName =~ /\d+\.\d+\.\d+/)[0])
+
+ props.setProperty ('appMainclass', "com.shatteredpixel.shatteredpixeldungeon.ios.IOSLauncher")
+ props.setProperty ('appExecutable', "IOSLauncher")
+
+ file("robovm.properties").withWriter { props.store(it, "Dynamically generated, do not commit to version control!") }
+}
+
+build.dependsOn updateRoboVMProps
+
+launchIPhoneSimulator.dependsOn build
+launchIPadSimulator.dependsOn build
+launchIOSDevice.dependsOn build
+createIPA.dependsOn build
+
+dependencies {
+ implementation project(':core')
+ implementation project(':services:updates:debugUpdates')
+ implementation project(':services:news:shatteredNews')
+
+ implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
+ implementation "com.mobidevelop.robovm:robovm-rt:$robovmVersion"
+ implementation "com.mobidevelop.robovm:robovm-cocoatouch:$robovmVersion"
+ implementation "com.badlogicgames.gdx:gdx-backend-robovm:$gdxVersion"
+ implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-ios"
+ implementation "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-ios"
+}
\ No newline at end of file
diff --git a/ios/robovm.xml b/ios/robovm.xml
new file mode 100644
index 000000000..95046370e
--- /dev/null
+++ b/ios/robovm.xml
@@ -0,0 +1,52 @@
+
+ ${appExecutable}
+ ${appMainclass}
+ ios
+ thumbv7
+ ios
+ Info.plist
+
+
+ ../core/src/main/assets
+
+ **
+
+ true
+
+
+ ../desktop/src/main/assets
+
+ **
+
+ true
+
+
+ assets
+
+
+
+ com.badlogic.gdx.scenes.scene2d.ui.*
+ com.badlogic.gdx.graphics.g3d.particles.**
+ com.android.okhttp.HttpHandler
+ com.android.okhttp.HttpsHandler
+ com.android.org.conscrypt.**
+ com.android.org.bouncycastle.jce.provider.BouncyCastleProvider
+ com.android.org.bouncycastle.jcajce.provider.keystore.BC$Mappings
+ com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi
+ com.android.org.bouncycastle.jcajce.provider.keystore.bc.BcKeyStoreSpi$Std
+ com.android.org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi
+ com.android.org.bouncycastle.crypto.digests.AndroidDigestFactoryOpenSSL
+ org.apache.harmony.security.provider.cert.DRLCertFactory
+ org.apache.harmony.security.provider.crypto.CryptoProvider
+
+
+ UIKit
+ OpenGLES
+ QuartzCore
+ CoreGraphics
+ OpenAL
+ AudioToolbox
+ AVFoundation
+ GameController
+
+
\ No newline at end of file
diff --git a/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSLauncher.java b/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSLauncher.java
new file mode 100644
index 000000000..265fb2398
--- /dev/null
+++ b/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSLauncher.java
@@ -0,0 +1,103 @@
+package com.shatteredpixel.shatteredpixeldungeon.ios;
+
+import com.badlogic.gdx.Files;
+import com.badlogic.gdx.backends.iosrobovm.IOSApplication;
+import com.badlogic.gdx.backends.iosrobovm.IOSApplicationConfiguration;
+import com.badlogic.gdx.graphics.glutils.HdpiMode;
+import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
+import com.shatteredpixel.shatteredpixeldungeon.services.news.News;
+import com.shatteredpixel.shatteredpixeldungeon.services.news.NewsImpl;
+import com.shatteredpixel.shatteredpixeldungeon.services.updates.UpdateImpl;
+import com.shatteredpixel.shatteredpixeldungeon.services.updates.Updates;
+import com.watabou.noosa.Game;
+import com.watabou.utils.FileUtils;
+
+import org.robovm.apple.coregraphics.CGRect;
+import org.robovm.apple.foundation.NSAutoreleasePool;
+import org.robovm.apple.foundation.NSBundle;
+import org.robovm.apple.foundation.NSDictionary;
+import org.robovm.apple.foundation.NSException;
+import org.robovm.apple.glkit.GLKViewDrawableColorFormat;
+import org.robovm.apple.glkit.GLKViewDrawableDepthFormat;
+import org.robovm.apple.uikit.UIApplication;
+
+public class IOSLauncher extends IOSApplication.Delegate {
+ @Override
+ protected IOSApplication createApplication() {
+
+ //ensures the app actually crashes if there's an error in the mobiVM runtime
+ Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ public void uncaughtException(Thread thread, Throwable ex) {
+ new NSException(ex.getClass().getName(), ex.getMessage(), new NSDictionary()).raise();
+ }
+ });
+
+ try {
+ Game.version = NSBundle.getMainBundle().getInfoDictionaryObject("CFBundleVersionString").toString();
+ } catch (Exception e) {
+ Game.version = "???";
+ }
+ try {
+ Game.versionCode = Integer.parseInt(NSBundle.getMainBundle().getInfoDictionaryObject("CFBundleVersion").toString());
+ } catch (Exception e) {
+ Game.versionCode = 0;
+ }
+
+ if (UpdateImpl.supportsUpdates()) {
+ Updates.service = UpdateImpl.getUpdateService();
+ }
+ if (NewsImpl.supportsNews()) {
+ News.service = NewsImpl.getNewsService();
+ }
+
+ FileUtils.setDefaultFileProperties(Files.FileType.Local, "");
+
+ IOSApplicationConfiguration config = new IOSApplicationConfiguration();
+
+ config.colorFormat = GLKViewDrawableColorFormat.RGBA8888;
+ config.depthFormat = GLKViewDrawableDepthFormat.None;
+ config.hdpiMode = HdpiMode.Pixels;
+
+ CGRect statusBarFrame = UIApplication.getSharedApplication().getStatusBarFrame();
+ double statusBarHeight = Math.min(statusBarFrame.getWidth(), statusBarFrame.getHeight());
+
+ //if the application has a short status bar (no notch), then hide it
+ //TODO we do this check elsewhere now, can this be removed?
+ if (statusBarHeight <= 24) {
+ UIApplication.getSharedApplication().setStatusBarHidden(true);
+ }
+
+ config.useAccelerometer = false;
+ config.useCompass = false;
+
+ //devices not currently listed in LibGDX's IOSDevice class
+ config.addIosDevice("IPHONE_12_MINI", "iPhone13,1", 476);
+ config.addIosDevice("IPHONE_12", "iPhone13,2", 460);
+ config.addIosDevice("IPHONE_12_PRO", "iPhone13,3", 460);
+ config.addIosDevice("IPHONE_12_PRO_MAX", "iPhone13,4", 458);
+
+ config.addIosDevice("IPAD_7G_WIFI", "iPad7,11", 264);
+ config.addIosDevice("IPAD_7G_WIFI_CELLULAR", "iPad7,12", 264);
+
+ config.addIosDevice("IPAD_8G_WIFI", "iPad11,6", 264);
+ config.addIosDevice("IPAD_8G_WIFI_CELLULAR", "iPad11,7", 264);
+ config.addIosDevice("IPAD_AIR_4G_WIFI", "iPad13,1", 264);
+ config.addIosDevice("IPAD_AIR_4G_WIFI_CELLULAR", "iPad13,2", 264);
+ config.addIosDevice("IPAD_PRO_11_3G", "iPad13,4", 264);
+ config.addIosDevice("IPAD_PRO_11_3G", "iPad13,5", 264);
+ config.addIosDevice("IPAD_PRO_11_3G", "iPad13,6", 264);
+ config.addIosDevice("IPAD_PRO_11_3G", "iPad13,7", 264);
+ config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,8", 264);
+ config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,9", 264);
+ config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,10", 264);
+ config.addIosDevice("IPAD_PRO_12.8_5G", "iPad13,11", 264);
+
+ return new IOSApplication(new ShatteredPixelDungeon(new IOSPlatformSupport()), config);
+ }
+
+ public static void main(String[] argv) {
+ NSAutoreleasePool pool = new NSAutoreleasePool();
+ UIApplication.main(argv, null, IOSLauncher.class);
+ pool.close();
+ }
+}
diff --git a/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSPlatformSupport.java b/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSPlatformSupport.java
new file mode 100644
index 000000000..f01e53976
--- /dev/null
+++ b/ios/src/main/java/com/shatteredpixel/shatteredpixeldungeon/ios/IOSPlatformSupport.java
@@ -0,0 +1,235 @@
+package com.shatteredpixel.shatteredpixeldungeon.ios;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.backends.iosrobovm.IOSGraphics;
+import com.badlogic.gdx.graphics.Pixmap;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.PixmapPacker;
+import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
+import com.shatteredpixel.shatteredpixeldungeon.SPDSettings;
+import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon;
+import com.watabou.noosa.Game;
+import com.watabou.utils.PlatformSupport;
+
+import org.robovm.apple.audiotoolbox.AudioServices;
+import org.robovm.apple.systemconfiguration.SCNetworkReachability;
+import org.robovm.apple.systemconfiguration.SCNetworkReachabilityFlags;
+import org.robovm.apple.uikit.UIApplication;
+
+import java.util.HashMap;
+import java.util.regex.Pattern;
+
+public class IOSPlatformSupport extends PlatformSupport {
+ @Override
+ public void updateDisplaySize() {
+ //non-zero safe insets on left/top/right means device has a notch, show status bar
+ if (Gdx.graphics.getSafeInsetTop() != 0
+ || Gdx.graphics.getSafeInsetLeft() != 0
+ || Gdx.graphics.getSafeInsetRight() != 0){
+ UIApplication.getSharedApplication().setStatusBarHidden(false);
+ } else {
+ UIApplication.getSharedApplication().setStatusBarHidden(true);
+ }
+
+ if (!SPDSettings.fullscreen()) {
+ Game.bottomInset = Gdx.graphics.getSafeInsetBottom();
+ Game.height -= Game.bottomInset;
+ Game.dispHeight = Game.height;
+ } else {
+ Game.height += Game.bottomInset;
+ Game.dispHeight = Game.height;
+ Game.bottomInset = 0;
+ }
+ Gdx.gl.glViewport(0, Game.bottomInset, Game.width, Game.height);
+ }
+
+ @Override
+ public void updateSystemUI() {
+ updateDisplaySize();
+ ShatteredPixelDungeon.seamlessResetScene();
+ }
+
+ @Override
+ public boolean connectedToUnmeteredNetwork() {
+ SCNetworkReachability test = new SCNetworkReachability("www.apple.com");
+ return !test.getFlags().contains(SCNetworkReachabilityFlags.IsWWAN);
+ }
+
+ @Override
+ public void promptTextInput(String title, String hintText, int maxLen, boolean multiLine, String posTxt, String negTxt, TextCallback callback) {
+ //TODO need multiplat text input, this does nothing atm!
+ }
+
+ public void vibrate( int millis ){
+ //gives a short vibrate on iPhone 6+, no vibration otherwise
+ AudioServices.playSystemSound(1520);
+ }
+
+ private int pageSize;
+ private PixmapPacker packer;
+ private boolean systemfont;
+
+ //custom pixel font, for use with Latin and Cyrillic languages
+ private static FreeTypeFontGenerator basicFontGenerator;
+ private static HashMap basicFonts = new HashMap<>();
+
+ //droid sans fallback, for asian fonts
+ private static FreeTypeFontGenerator asianFontGenerator;
+ private static HashMap asianFonts = new HashMap<>();
+
+ private static HashMap> fonts;
+
+ @Override
+ public void setupFontGenerators(int pageSize, boolean systemfont) {
+ //don't bother doing anything if nothing has changed
+ if (fonts != null && this.pageSize == pageSize && this.systemfont == systemfont){
+ return;
+ }
+ this.pageSize = pageSize;
+ this.systemfont = systemfont;
+
+ if (fonts != null){
+ for (FreeTypeFontGenerator generator : fonts.keySet()){
+ for (BitmapFont f : fonts.get(generator).values()){
+ f.dispose();
+ }
+ fonts.get(generator).clear();
+ generator.dispose();
+ }
+ fonts.clear();
+ if (packer != null){
+ for (PixmapPacker.Page p : packer.getPages()){
+ p.getTexture().dispose();
+ }
+ packer.dispose();
+ }
+ }
+ fonts = new HashMap<>();
+
+ if (systemfont) {
+ basicFontGenerator = asianFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/droid_sans.ttf"));
+ } else {
+ basicFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/pixel_font.ttf"));
+ asianFontGenerator = new FreeTypeFontGenerator(Gdx.files.internal("fonts/droid_sans.ttf"));
+ }
+
+ fonts.put(basicFontGenerator, basicFonts);
+ fonts.put(asianFontGenerator, asianFonts);
+
+ packer = new PixmapPacker(pageSize, pageSize, Pixmap.Format.RGBA8888, 1, false);
+ }
+
+ @Override
+ public void resetGenerators() {
+ if (fonts != null) {
+ for (FreeTypeFontGenerator generator : fonts.keySet()) {
+ for (BitmapFont f : fonts.get(generator).values()) {
+ f.dispose();
+ }
+ fonts.get(generator).clear();
+ generator.dispose();
+ }
+ fonts.clear();
+ if (packer != null) {
+ for (PixmapPacker.Page p : packer.getPages()) {
+ p.getTexture().dispose();
+ }
+ packer.dispose();
+ }
+ fonts = null;
+ }
+ setupFontGenerators(pageSize, systemfont);
+ }
+
+ @Override
+ public void reloadGenerators() {
+ if (packer != null) {
+ for (PixmapPacker.Page p : packer.getPages()) {
+ p.getTexture().dispose();
+ p.updateTexture(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest, false);
+ }
+ }
+ }
+
+ private static Pattern asianMatcher = Pattern.compile("\\p{InHangul_Syllables}|" +
+ "\\p{InCJK_Unified_Ideographs}|\\p{InCJK_Symbols_and_Punctuation}|\\p{InHalfwidth_and_Fullwidth_Forms}|" +
+ "\\p{InHiragana}|\\p{InKatakana}");
+
+ private static FreeTypeFontGenerator getGeneratorForString( String input ){
+ if (asianMatcher.matcher(input).find()){
+ return asianFontGenerator;
+ } else {
+ return basicFontGenerator;
+ }
+ }
+
+
+ @Override
+ public BitmapFont getFont(int size, String text) {
+ FreeTypeFontGenerator generator = getGeneratorForString(text);
+
+ if (generator == null){
+ return null;
+ }
+
+ if (!fonts.get(generator).containsKey(size)) {
+ FreeTypeFontGenerator.FreeTypeFontParameter parameters = new FreeTypeFontGenerator.FreeTypeFontParameter();
+ parameters.size = size;
+ parameters.flip = true;
+ parameters.borderWidth = parameters.size / 10f;
+ if (size >= 20){
+ parameters.renderCount = 2;
+ } else {
+ parameters.renderCount = 3;
+ }
+ parameters.hinting = FreeTypeFontGenerator.Hinting.None;
+ parameters.spaceX = -(int) parameters.borderWidth;
+ parameters.incremental = true;
+ if (generator == basicFontGenerator){
+ //if we're using latin/cyrillic, we can safely pre-generate some common letters
+ //(we define common as >4% frequency in english)
+ parameters.characters = "�etaoinshrdl";
+ } else {
+ parameters.characters = "�";
+ }
+ parameters.packer = packer;
+
+ try {
+ BitmapFont font = generator.generateFont(parameters);
+ font.getData().missingGlyph = font.getData().getGlyph('�');
+ fonts.get(generator).put(size, font);
+ } catch ( Exception e ){
+ Game.reportException(e);
+ return null;
+ }
+ }
+
+ return fonts.get(generator).get(size);
+ }
+
+ //splits on newlines, underscores, and chinese/japaneses characters
+ private Pattern regularsplitter = Pattern.compile(
+ "(?<=\n)|(?=\n)|(?<=_)|(?=_)|" +
+ "(?<=\\p{InHiragana})|(?=\\p{InHiragana})|" +
+ "(?<=\\p{InKatakana})|(?=\\p{InKatakana})|" +
+ "(?<=\\p{InCJK_Unified_Ideographs})|(?=\\p{InCJK_Unified_Ideographs})|" +
+ "(?<=\\p{InCJK_Symbols_and_Punctuation})|(?=\\p{InCJK_Symbols_and_Punctuation})");
+
+ //additionally splits on words, so that each word can be arranged individually
+ private Pattern regularsplitterMultiline = Pattern.compile(
+ "(?<= )|(?= )|(?<=\n)|(?=\n)|(?<=_)|(?=_)|" +
+ "(?<=\\p{InHiragana})|(?=\\p{InHiragana})|" +
+ "(?<=\\p{InKatakana})|(?=\\p{InKatakana})|" +
+ "(?<=\\p{InCJK_Unified_Ideographs})|(?=\\p{InCJK_Unified_Ideographs})|" +
+ "(?<=\\p{InCJK_Symbols_and_Punctuation})|(?=\\p{InCJK_Symbols_and_Punctuation})");
+
+ @Override
+ public String[] splitforTextBlock(String text, boolean multiline) {
+ if (multiline) {
+ return regularsplitterMultiline.split(text);
+ } else {
+ return regularsplitter.split(text);
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 06010c0cf..08d40a204 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,3 @@
-include ':core', ':SPD-classes', ':android', ':desktop', ':services',
+include ':core', ':SPD-classes', ':android', ':ios', ':desktop', ':services',
':services:updates:debugUpdates', ':services:updates:githubUpdates',
':services:news:debugNews', ':services:news:shatteredNews'
\ No newline at end of file