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