diff --git a/README.md b/README.md index d1b06abf3..653da7cb1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This project uses the following libraries: - [TeaVM](https://teavm.org), licensed under the Apache License, Version 2.0. - [gdx-teavm](https://github.com/xpenatan/gdx-teavm), licensed under the Apache License, Version 2.0. -[Shattered Pixel Dungeon](https://shatteredpixel.com/shatteredpd/) is an open-source traditional roguelike dungeon crawler with randomized levels and enemies, and hundreds of items to collect and use. It's based on the [source code of Pixel Dungeon](https://github.com/00-Evan/pixel-dungeon-gradle), by [Watabou](https://www.watabou.ru). +[Shattered Pixel Dungeon](https://shatteredpixel.com/shatteredpd/) is an open-source traditional roguelike dungeon crawler with randomized levels and enemies, and hundreds of items to collect and use. It's based on the [source code of Pixel Dungeon](https://github.com/00-Evan/pixel-dungeon-gradle), by [Watabou](https://watabou.itch.io/). Shattered Pixel Dungeon currently compiles for Android, iOS, and Desktop platforms. You can find official releases of the game on: @@ -22,7 +22,7 @@ If you like this game, please consider [supporting me on Patreon](https://www.pa There is an official blog for this project at [ShatteredPixel.com](https://www.shatteredpixel.com/blog/). -The game also has a translation project hosted on [Transifex](https://www.transifex.com/shattered-pixel/shattered-pixel-dungeon/). +The game also has a translation project hosted on [Transifex](https://explore.transifex.com/shattered-pixel/shattered-pixel-dungeon/). Note that **this repository does not accept pull requests!** The code here is provided in hopes that others may find it useful for their own projects, not to allow community contribution. Issue reports of all kinds (bug reports, feature requests, etc.) are welcome. diff --git a/SPD-classes/build.gradle b/SPD-classes/build.gradle index f6b5e1c39..3d7644b6d 100644 --- a/SPD-classes/build.gradle +++ b/SPD-classes/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'java-library' [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' -sourceCompatibility = targetCompatibility = appJavaCompatibility +java.sourceCompatibility = java.targetCompatibility = appJavaCompatibility dependencies { api "com.badlogicgames.gdx:gdx:$gdxVersion" diff --git a/SPD-classes/src/main/java/com/watabou/input/ControllerHandler.java b/SPD-classes/src/main/java/com/watabou/input/ControllerHandler.java index 8afd01978..8ad9e087e 100644 --- a/SPD-classes/src/main/java/com/watabou/input/ControllerHandler.java +++ b/SPD-classes/src/main/java/com/watabou/input/ControllerHandler.java @@ -30,7 +30,6 @@ import com.badlogic.gdx.controllers.ControllerMapping; import com.badlogic.gdx.controllers.Controllers; import com.watabou.noosa.Game; import com.watabou.noosa.ui.Cursor; -import com.watabou.utils.DeviceCompat; import com.watabou.utils.PointF; public class ControllerHandler implements ControllerListener { @@ -65,9 +64,7 @@ public class ControllerHandler implements ControllerListener { private static boolean failedInit = false; public static boolean controllersSupported() { - if (DeviceCompat.isAndroid() && Gdx.app.getVersion() < 16) { - return false; - } else if (failedInit) { + if (failedInit) { return false; } else if (initialized){ return true; diff --git a/SPD-classes/src/main/java/com/watabou/input/InputHandler.java b/SPD-classes/src/main/java/com/watabou/input/InputHandler.java index 2bc99b4ed..e19aed69f 100644 --- a/SPD-classes/src/main/java/com/watabou/input/InputHandler.java +++ b/SPD-classes/src/main/java/com/watabou/input/InputHandler.java @@ -33,36 +33,7 @@ public class InputHandler extends InputAdapter { private InputMultiplexer multiplexer; public InputHandler( Input input ){ - //An input multiplexer, with additional coord tweaks for power saver mode - multiplexer = new InputMultiplexer(){ - @Override - public boolean touchDown(int screenX, int screenY, int pointer, int button) { - screenX /= (Game.dispWidth / (float)Game.width); - screenY /= (Game.dispHeight / (float)Game.height); - return super.touchDown(screenX, screenY, pointer, button); - } - - @Override - public boolean touchDragged(int screenX, int screenY, int pointer) { - screenX /= (Game.dispWidth / (float)Game.width); - screenY /= (Game.dispHeight / (float)Game.height); - return super.touchDragged(screenX, screenY, pointer); - } - - @Override - public boolean touchUp(int screenX, int screenY, int pointer, int button) { - screenX /= (Game.dispWidth / (float)Game.width); - screenY /= (Game.dispHeight / (float)Game.height); - return super.touchUp(screenX, screenY, pointer, button); - } - - @Override - public boolean mouseMoved(int screenX, int screenY) { - screenX /= (Game.dispWidth / (float)Game.width); - screenY /= (Game.dispHeight / (float)Game.height); - return super.mouseMoved(screenX, screenY); - } - }; + multiplexer = new InputMultiplexer(); input.setInputProcessor(multiplexer); addInputProcessor(this); input.setCatchKey( Input.Keys.BACK, true); diff --git a/SPD-classes/src/main/java/com/watabou/input/PointerEvent.java b/SPD-classes/src/main/java/com/watabou/input/PointerEvent.java index a5141022d..5d7600a78 100644 --- a/SPD-classes/src/main/java/com/watabou/input/PointerEvent.java +++ b/SPD-classes/src/main/java/com/watabou/input/PointerEvent.java @@ -183,7 +183,7 @@ public class PointerEvent { } if (clearKeyboardThisPress){ //most press events should clear the keyboard - Game.platform.setOnscreenKeyboardVisible(false); + Game.platform.setOnscreenKeyboardVisible(false, false); } } pointerEvents.clear(); diff --git a/SPD-classes/src/main/java/com/watabou/noosa/Camera.java b/SPD-classes/src/main/java/com/watabou/noosa/Camera.java index 78f0ac8de..23b64d85f 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/Camera.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/Camera.java @@ -177,7 +177,7 @@ public class Camera extends Gizmo { float deadX = 0; float deadY = 0; - if (followTarget != null){ + if (followTarget != null && followTarget.visible){ //manually assign here to avoid an allocation from sprite.center() panTarget.x = followTarget.x + followTarget.width()/2; panTarget.y = followTarget.y + followTarget.height()/2; diff --git a/SPD-classes/src/main/java/com/watabou/noosa/Game.java b/SPD-classes/src/main/java/com/watabou/noosa/Game.java index d6b9bbe49..0f73ea0e3 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/Game.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/Game.java @@ -21,7 +21,6 @@ package com.watabou.noosa; -import com.badlogic.gdx.Application; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.controllers.Controllers; @@ -46,18 +45,11 @@ import java.io.StringWriter; public class Game implements ApplicationListener { public static Game instance; - - //actual size of the display - public static int dispWidth; - public static int dispHeight; // Size of the EGL surface view public static int width; public static int height; - //number of pixels from bottom of view before rendering starts - public static int bottomInset; - // Density: mdpi=1, hdpi=1.5, xhdpi=2... public static float density = 1; @@ -93,13 +85,12 @@ public class Game implements ApplicationListener { @Override public void create() { - dispHeight = Gdx.graphics.getDisplayMode().height; - dispWidth = Gdx.graphics.getDisplayMode().width; - density = Gdx.graphics.getDensity(); if (density == Float.POSITIVE_INFINITY){ density = 100f / 160f; //assume 100PPI if density can't be found } else if (DeviceCompat.isDesktop()) { + int dispWidth = Gdx.graphics.getDisplayMode().width; + int dispHeight = Gdx.graphics.getDisplayMode().height; float reportedWidth = dispWidth / Gdx.graphics.getPpiX(); float reportedHeight = dispHeight / Gdx.graphics.getPpiY(); @@ -142,18 +133,11 @@ public class Game implements ApplicationListener { Vertexbuffer.reload(); } - height -= bottomInset; if (height != Game.height || width != Game.width) { Game.width = width; Game.height = height; - //TODO might be better to put this in platform support - if (Gdx.app.getType() != Application.ApplicationType.Android){ - Game.dispWidth = Game.width; - Game.dispHeight = Game.height; - } - resetScene(); } } @@ -283,7 +267,9 @@ public class Game implements ApplicationListener { } protected void update() { - Game.elapsed = Game.timeScale * Gdx.graphics.getDeltaTime(); + //game will not process more than 200ms of graphics time per frame + float frameDelta = Math.min(0.2f, Gdx.graphics.getDeltaTime()); + Game.elapsed = Game.timeScale * frameDelta; Game.timeTotal += Game.elapsed; Game.realTime = TimeUtils.millis(); diff --git a/SPD-classes/src/main/java/com/watabou/noosa/MovieClip.java b/SPD-classes/src/main/java/com/watabou/noosa/MovieClip.java index ae99700f0..2541cf8ad 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/MovieClip.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/MovieClip.java @@ -63,9 +63,11 @@ public class MovieClip extends Image { while (frameTimer > curAnim.delay) { frameTimer -= curAnim.delay; if (curFrame >= curAnim.frames.length - 1) { - curFrame = curAnim.frames.length - 1; if (curAnim.looped) { curFrame = 0; + } else { + curFrame = curAnim.frames.length - 1; + frameTimer = 0; } finished = true; if (listener != null) { diff --git a/SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java b/SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java index 544476a92..c946c99c6 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/NoosaScript.java @@ -29,6 +29,7 @@ import com.watabou.glwrap.Attribute; import com.watabou.glwrap.Quad; import com.watabou.glwrap.Uniform; import com.watabou.glwrap.Vertexbuffer; +import com.watabou.utils.DeviceCompat; import java.nio.Buffer; import java.nio.FloatBuffer; @@ -213,12 +214,12 @@ public class NoosaScript extends Script { //This fixes pixel scaling issues on some hidpi displays (mainly on macOS) // because for some reason all other openGL operations work on virtual pixels // but glScissor operations work on real pixels - float xScale = (Gdx.graphics.getBackBufferWidth() / (float)Game.width ); - float yScale = ((Gdx.graphics.getBackBufferHeight()-Game.bottomInset) / (float)Game.height ); + float xScale = DeviceCompat.getRealPixelScaleX(); + float yScale = DeviceCompat.getRealPixelScaleY(); activeGL.glScissor( Math.round(camera.x * xScale), - Math.round((Game.height - camera.screenHeight - camera.y) * yScale) + Game.bottomInset, + Math.round((Game.height - camera.screenHeight - camera.y) * yScale), Math.round(camera.screenWidth * xScale), Math.round(camera.screenHeight * yScale)); } else { diff --git a/SPD-classes/src/main/java/com/watabou/noosa/TextInput.java b/SPD-classes/src/main/java/com/watabou/noosa/TextInput.java index 632fe3c01..5d5673394 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/TextInput.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/TextInput.java @@ -23,8 +23,10 @@ package com.watabou.noosa; import com.badlogic.gdx.Files; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.Mesh; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.ui.Container; @@ -62,8 +64,11 @@ public class TextInput extends Component { //use a custom viewport here to ensure stage camera matches game camera Viewport viewport = new Viewport() {}; viewport.setWorldSize(Game.width, Game.height); - viewport.setScreenBounds(0, Game.bottomInset, Game.width, Game.height); + viewport.setScreenBounds(0, 0, Game.width, Game.height); viewport.setCamera(new OrthographicCamera()); + //TODO this is needed for the moment as Spritebatch switched to using VAOs in libGDX v1.13.1 + // This results in HARD crashes atm, whereas old vertex arrays work fine + SpriteBatch.overrideVertexType = Mesh.VertexDataType.VertexArray; stage = new Stage(viewport); Game.inputHandler.addInputProcessor(stage); @@ -136,13 +141,13 @@ public class TextInput extends Component { textField.setOnscreenKeyboard(new TextField.OnscreenKeyboard() { @Override public void show(boolean visible) { - Game.platform.setOnscreenKeyboardVisible(visible); + Game.platform.setOnscreenKeyboardVisible(visible, multiline); } }); container.setActor(textField); stage.setKeyboardFocus(textField); - Game.platform.setOnscreenKeyboardVisible(true); + Game.platform.setOnscreenKeyboardVisible(true, multiline); } public void enterPressed(){ @@ -253,7 +258,7 @@ public class TextInput extends Component { stage.dispose(); skin.dispose(); Game.inputHandler.removeInputProcessor(stage); - Game.platform.setOnscreenKeyboardVisible(false); + Game.platform.setOnscreenKeyboardVisible(false, false); if (!DeviceCompat.isDesktop()) Game.platform.updateSystemUI(); } } diff --git a/SPD-classes/src/main/java/com/watabou/noosa/TextureFilm.java b/SPD-classes/src/main/java/com/watabou/noosa/TextureFilm.java index 53ed9ca5c..b3d4b591c 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/TextureFilm.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/TextureFilm.java @@ -90,6 +90,26 @@ public class TextureFilm { } } } + + //creates a film for a texture with known size without needing to reference it + public TextureFilm( int txWidth, int txHeight, int width, int height){ + + texWidth = txWidth; + texHeight = txHeight; + + float uw = (float)width / texWidth; + float vh = (float)height / texHeight; + int cols = texWidth / width; + int rows = texHeight / height; + + for (int i=0; i < rows; i++) { + for (int j=0; j < cols; j++) { + RectF rect = new RectF( j * uw, i * vh, (j+1) * uw, (i+1) * vh ); + add( i * cols + j, rect ); + } + } + + } public void add( Object id, RectF rect ) { frames.put( id, rect ); diff --git a/SPD-classes/src/main/java/com/watabou/noosa/audio/Sample.java b/SPD-classes/src/main/java/com/watabou/noosa/audio/Sample.java index 67e7a41a6..ac5b987cf 100644 --- a/SPD-classes/src/main/java/com/watabou/noosa/audio/Sample.java +++ b/SPD-classes/src/main/java/com/watabou/noosa/audio/Sample.java @@ -24,7 +24,6 @@ package com.watabou.noosa.audio; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.audio.Sound; import com.watabou.noosa.Game; -import com.watabou.utils.Callback; import java.util.HashMap; import java.util.HashSet; @@ -62,44 +61,30 @@ public enum Sample { } } - private static LinkedList loadingQueue = new LinkedList<>(); - - public synchronized void load( final String... assets ) { - - for (String asset : assets){ - if (!ids.containsKey(asset) && !loadingQueue.contains(asset)){ - loadingQueue.add(asset); + public synchronized void load( final String asset){ + if (asset != null) { + try { + Sound newSound = Gdx.audio.newSound(Gdx.files.internal(asset)); + ids.put(asset, newSound); + } catch (Exception e){ + Game.reportException(e); } } - - //cancel if all assets are already loaded - if (loadingQueue.isEmpty()) return; - - //load one at a time on the UI thread to prevent this blocking the UI - //yes this may cause hitching, but only in the first couple seconds of game runtime - Game.runOnRenderThread(loadingCallback); - } - private Callback loadingCallback = new Callback() { - @Override - public void call() { - synchronized (INSTANCE) { - String asset = loadingQueue.poll(); - if (asset != null) { - try { - Sound newSound = Gdx.audio.newSound(Gdx.files.internal(asset)); - ids.put(asset, newSound); - } catch (Exception e){ - Game.reportException(e); - } - } - if (!loadingQueue.isEmpty()){ - Game.runOnRenderThread(this); + private static final LinkedList loadingQueue = new LinkedList<>(); + + //queues multiple assets for loading, which happens in update() + // this prevents blocking while we load many assets + public void load( final String[] assets ) { + synchronized (loadingQueue) { + for (String asset : assets) { + if (!ids.containsKey(asset) && !loadingQueue.contains(asset)) { + loadingQueue.add(asset); } } } - }; + } public synchronized void unload( Object src ) { if (ids.containsKey( src )) { @@ -170,6 +155,12 @@ public enum Sample { } public void update(){ + synchronized (loadingQueue) { + if (!loadingQueue.isEmpty()) { + load(loadingQueue.poll()); + } + } + synchronized (delayedSFX) { if (delayedSFX.isEmpty()) return; for (DelayedSoundEffect sfx : delayedSFX.toArray(new DelayedSoundEffect[0])) { diff --git a/SPD-classes/src/main/java/com/watabou/utils/DeviceCompat.java b/SPD-classes/src/main/java/com/watabou/utils/DeviceCompat.java index 3d30d7dd0..deadd90a0 100644 --- a/SPD-classes/src/main/java/com/watabou/utils/DeviceCompat.java +++ b/SPD-classes/src/main/java/com/watabou/utils/DeviceCompat.java @@ -29,20 +29,6 @@ import com.badlogic.gdx.Application; //TODO migrate to platformSupport class public class DeviceCompat { - - public static boolean supportsFullScreen(){ - switch (Gdx.app.getType()){ - case Android: - //Android 4.4+ supports hiding UI via immersive mode - return Gdx.app.getVersion() >= 19; - case iOS: - //iOS supports hiding UI via drawing into the gesture safe area - return Gdx.graphics.getSafeInsetBottom() != 0; - default: - //TODO implement functionality for other platforms here - return true; - } - } //return APi level on Android, major OS version on iOS, 0 on desktop public static int getPlatformVersion(){ @@ -80,13 +66,15 @@ public class DeviceCompat { Gdx.app.log( tag, message ); } - public static RectF getSafeInsets(){ - RectF result = new RectF(); - result.left = Gdx.graphics.getSafeInsetLeft(); - result.top = Gdx.graphics.getSafeInsetTop(); - result.right = Gdx.graphics.getSafeInsetRight(); - result.bottom = Gdx.graphics.getSafeInsetBottom(); - return result; + //some devices (macOS mainly) report virtual pixels to Shattered, but sometimes we want real pixel precision + //this returns the number of real pixels per virtual pixel in the X dimension... + public static float getRealPixelScaleX(){ + return (Gdx.graphics.getBackBufferWidth() / (float)Game.width ); + } + + //...and in the Y dimension + public static float getRealPixelScaleY(){ + return (Gdx.graphics.getBackBufferHeight() / (float)Game.height ); } } diff --git a/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java b/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java index b07f65850..2d016643f 100644 --- a/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java +++ b/SPD-classes/src/main/java/com/watabou/utils/PlatformSupport.java @@ -22,6 +22,7 @@ package com.watabou.utils; import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.PixmapPacker; @@ -34,6 +35,28 @@ import java.util.HashMap; public abstract class PlatformSupport { public abstract void updateDisplaySize(); + + public boolean supportsFullScreen(){ + return true; //default + } + + public static final int INSET_ALL = 3; //All insets, from hole punches to nav bars + public static final int INSET_LRG = 2; //Only big insets, full size notches and nav bars + public static final int INSET_BLK = 1; //only complete blocker assets like navbars + + public RectF getSafeInsets( int level ){ + return new RectF( + Gdx.graphics.getSafeInsetLeft(), + Gdx.graphics.getSafeInsetTop(), + Gdx.graphics.getSafeInsetRight(), + Gdx.graphics.getSafeInsetBottom() + ); + } + + //returns a display cutout (if one is present) in device pixels, or null is none is present + public RectF getDisplayCutout(){ + return null; + } public abstract void updateSystemUI(); @@ -57,8 +80,9 @@ public abstract class PlatformSupport { return Gdx.net.openURI( uri ); } - public void setOnscreenKeyboardVisible(boolean value){ - Gdx.input.setOnscreenKeyboardVisible(value); + public void setOnscreenKeyboardVisible(boolean value, boolean multiline){ + //by default ignore multiline + Gdx.input.setOnscreenKeyboardVisible(value, Input.OnscreenKeyboardType.Default); } //TODO should consider spinning this into its own class, rather than platform support getting ever bigger diff --git a/android/build.gradle b/android/build.gradle index 0224500bd..5146d5d6c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -59,19 +59,16 @@ configurations { natives } dependencies { implementation project(':core') - // We are using the natives from 1.11.0 to maintain compatibility with Android 4.0-4.3 for now - // The 1.12.0+ natives are compiled with a version of the NDK which drops 4.3- support - // YES, THIS IS VERY DANGEROUS! 1.11.0 natives may only incidentally work with later libGDX versions implementation "com.badlogicgames.gdx:gdx-backend-android:$gdxVersion" - natives "com.badlogicgames.gdx:gdx-platform:1.11.0:natives-armeabi-v7a" - natives "com.badlogicgames.gdx:gdx-platform:1.11.0:natives-arm64-v8a" - natives "com.badlogicgames.gdx:gdx-platform:1.11.0:natives-x86" - natives "com.badlogicgames.gdx:gdx-platform:1.11.0:natives-x86_64" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a" + natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64" implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion" - natives "com.badlogicgames.gdx:gdx-freetype-platform:1.11.0:natives-armeabi-v7a" - natives "com.badlogicgames.gdx:gdx-freetype-platform:1.11.0:natives-arm64-v8a" - natives "com.badlogicgames.gdx:gdx-freetype-platform:1.11.0:natives-x86" - natives "com.badlogicgames.gdx:gdx-freetype-platform:1.11.0:natives-x86_64" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86" + natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86_64" implementation "com.badlogicgames.gdx-controllers:gdx-controllers-android:$gdxControllersVersion" } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 25a1f38d4..cdbcc0c82 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,22 +1,15 @@ + android:targetSandboxVersion="2"> - - - - - + diff --git a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidLauncher.java b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidLauncher.java index 6169b2d2f..f73f0f4ec 100644 --- a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidLauncher.java +++ b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidLauncher.java @@ -24,7 +24,6 @@ package com.shatteredpixel.shatteredpixeldungeon.android; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; @@ -122,21 +121,8 @@ public class AndroidLauncher extends AndroidApplication { }); } - //set desired orientation (if it exists) before initializing the app. - if (SPDSettings.landscape() != null) { - instance.setRequestedOrientation( SPDSettings.landscape() ? - ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT ); - } - AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); config.depth = 0; - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - //use rgb565 on ICS devices for better performance - config.r = 5; - config.g = 6; - config.b = 5; - } //we manage this ourselves config.useImmersiveMode = false; @@ -164,11 +150,7 @@ public class AndroidLauncher extends AndroidApplication { protected void onResume() { //prevents weird rare cases where the app is running twice if (instance != this){ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - finishAndRemoveTask(); - } else { - finish(); - } + finishAndRemoveTask(); } super.onResume(); } diff --git a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java index 06af7fb7b..72c0d10b9 100644 --- a/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java +++ b/android/src/main/java/com/shatteredpixel/shatteredpixeldungeon/android/AndroidPlatformSupport.java @@ -21,25 +21,25 @@ package com.shatteredpixel.shatteredpixeldungeon.android; -import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ActivityInfo; +import android.graphics.Rect; import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.opengl.GLSurfaceView; import android.os.Build; +import android.view.DisplayCutout; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import com.badlogic.gdx.Gdx; -import com.badlogic.gdx.backends.android.AndroidGraphics; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.g2d.PixmapPacker; import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator; import com.shatteredpixel.shatteredpixeldungeon.SPDSettings; -import com.shatteredpixel.shatteredpixeldungeon.scenes.PixelScene; +import com.shatteredpixel.shatteredpixeldungeon.ShatteredPixelDungeon; import com.watabou.noosa.Game; import com.watabou.utils.PlatformSupport; +import com.watabou.utils.RectF; import java.util.HashMap; import java.util.regex.Matcher; @@ -48,78 +48,70 @@ import java.util.regex.Pattern; public class AndroidPlatformSupport extends PlatformSupport { public void updateDisplaySize(){ - if (SPDSettings.landscape() != null) { - AndroidLauncher.instance.setRequestedOrientation( SPDSettings.landscape() ? - ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT ); - } + AndroidLauncher.instance.setRequestedOrientation( SPDSettings.landscape() ? + ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : + ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED ); - GLSurfaceView view = (GLSurfaceView) ((AndroidGraphics)Gdx.graphics).getView(); - - if (view.getMeasuredWidth() == 0 || view.getMeasuredHeight() == 0) - return; - - Game.dispWidth = view.getMeasuredWidth(); - Game.dispHeight = view.getMeasuredHeight(); + ShatteredPixelDungeon.seamlessResetScene(); + } - boolean fullscreen = Build.VERSION.SDK_INT < Build.VERSION_CODES.N - || !AndroidLauncher.instance.isInMultiWindowMode(); - - if (fullscreen && SPDSettings.landscape() != null - && (Game.dispWidth >= Game.dispHeight) != SPDSettings.landscape()){ - int tmp = Game.dispWidth; - Game.dispWidth = Game.dispHeight; - Game.dispHeight = tmp; - } - - float dispRatio = Game.dispWidth / (float)Game.dispHeight; - - float renderWidth = dispRatio > 1 ? PixelScene.MIN_WIDTH_L : PixelScene.MIN_WIDTH_P; - float renderHeight = dispRatio > 1 ? PixelScene.MIN_HEIGHT_L : PixelScene.MIN_HEIGHT_P; - - //force power saver in this case as all devices must run at at least 2x scale. - if (Game.dispWidth < renderWidth*2 || Game.dispHeight < renderHeight*2) - SPDSettings.put( SPDSettings.KEY_POWER_SAVER, true ); - - if (SPDSettings.powerSaver() && fullscreen){ - - int maxZoom = (int)Math.min(Game.dispWidth/renderWidth, Game.dispHeight/renderHeight); - - renderWidth *= Math.max( 2, Math.round(1f + maxZoom*0.4f)); - renderHeight *= Math.max( 2, Math.round(1f + maxZoom*0.4f)); - - if (dispRatio > renderWidth / renderHeight){ - renderWidth = renderHeight * dispRatio; - } else { - renderHeight = renderWidth / dispRatio; - } - - final int finalW = Math.round(renderWidth); - final int finalH = Math.round(renderHeight); - if (finalW != Game.width || finalH != Game.height){ - - AndroidLauncher.instance.runOnUiThread(new Runnable() { - @Override - public void run() { - view.getHolder().setFixedSize(finalW, finalH); - } - }); - - } + public boolean supportsFullScreen(){ + //We support hiding the navigation bar or gesture bar, if it is present + // on Android 9+ we check for this, on earlier just assume it's present + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowInsets insets = AndroidLauncher.instance.getApplicationWindow().getDecorView().getRootWindowInsets(); + return insets != null && (insets.getStableInsetBottom() > 0 || insets.getStableInsetRight() > 0 || insets.getStableInsetLeft() > 0); } else { - AndroidLauncher.instance.runOnUiThread(new Runnable() { - @Override - public void run() { - view.getHolder().setSizeFromLayout(); - } - }); + return true; } } - + + @Override + public RectF getSafeInsets( int level ) { + RectF insets = new RectF(); + + //getting insets technically works down to 6.0 Marshmallow, but we let the device handle all of that prior to 9.0 Pie + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !AndroidLauncher.instance.isInMultiWindowMode()) { + WindowInsets rootInsets = AndroidLauncher.instance.getApplicationWindow().getDecorView().getRootWindowInsets(); + if (rootInsets != null) { + + //Navigation bar (never on the top) + if (supportsFullScreen() && !SPDSettings.fullscreen()) { + insets.left = Math.max(insets.left, rootInsets.getStableInsetLeft()); + insets.right = Math.max(insets.right, rootInsets.getStableInsetRight()); + insets.bottom = Math.max(insets.bottom, rootInsets.getStableInsetBottom()); + } + + //display cutout + if (level > INSET_BLK) { + DisplayCutout cutout = rootInsets.getDisplayCutout(); + + if (cutout != null) { + boolean largeCutout = false; + int screenSize = Game.width * Game.height; + for (Rect r : cutout.getBoundingRects()){ + int cutoutSize = Math.abs(r.height() * r.width()); + //display cutouts are considered large if they take up more than 0.5% of the screen + if (cutoutSize*200 >= screenSize){ + largeCutout = true; + } + } + if (largeCutout || level == INSET_ALL) { + insets.left = Math.max(insets.left, cutout.getSafeInsetLeft()); + insets.top = Math.max(insets.top, cutout.getSafeInsetTop()); + insets.right = Math.max(insets.right, cutout.getSafeInsetRight()); + insets.bottom = Math.max(insets.bottom, cutout.getSafeInsetBottom()); + } + } + } + } + } + return insets; + } + public void updateSystemUI() { AndroidLauncher.instance.runOnUiThread(new Runnable() { - @SuppressLint("NewApi") @Override public void run() { boolean fullscreen = Build.VERSION.SDK_INT < Build.VERSION_CODES.N @@ -132,17 +124,16 @@ public class AndroidPlatformSupport extends PlatformSupport { AndroidLauncher.instance.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ - if (SPDSettings.fullscreen()) { - AndroidLauncher.instance.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); - } else { - AndroidLauncher.instance.getWindow().getDecorView().setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE ); - } + + if (supportsFullScreen() && SPDSettings.fullscreen()) { + AndroidLauncher.instance.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); + } else { + //still want to hide the status bar and cutout void + AndroidLauncher.instance.getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN ); } } }); @@ -150,20 +141,9 @@ public class AndroidPlatformSupport extends PlatformSupport { } @Override - @SuppressWarnings("deprecation") public boolean connectedToUnmeteredNetwork() { - //Returns true if using unmetered connection, use shortcut method if available - ConnectivityManager cm = (ConnectivityManager) AndroidLauncher.instance.getSystemService(Context.CONNECTIVITY_SERVICE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ - return !cm.isActiveNetworkMetered(); - } else { - NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); - return activeNetwork != null && activeNetwork.isConnectedOrConnecting() && - (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI - || activeNetwork.getType() == ConnectivityManager.TYPE_WIMAX - || activeNetwork.getType() == ConnectivityManager.TYPE_BLUETOOTH - || activeNetwork.getType() == ConnectivityManager.TYPE_ETHERNET); - } + //Returns true if using unmetered connection + return !((ConnectivityManager) AndroidLauncher.instance.getSystemService(Context.CONNECTIVITY_SERVICE)).isActiveNetworkMetered(); } @Override @@ -177,8 +157,8 @@ public class AndroidPlatformSupport extends PlatformSupport { private static FreeTypeFontGenerator basicFontGenerator; //droid sans / nanum gothic / noto sans, for use with Korean private static FreeTypeFontGenerator KRFontGenerator; - //droid sans / noto sans, for use with Simplified Chinese - private static FreeTypeFontGenerator SCFontGenerator; + //droid sans / noto sans, for use with Chinese + private static FreeTypeFontGenerator ZHFontGenerator; //droid sans / noto sans, for use with Japanese private static FreeTypeFontGenerator JPFontGenerator; @@ -196,7 +176,7 @@ public class AndroidPlatformSupport extends PlatformSupport { resetGenerators(false); fonts = new HashMap<>(); - basicFontGenerator = KRFontGenerator = SCFontGenerator = JPFontGenerator = null; + basicFontGenerator = KRFontGenerator = ZHFontGenerator = JPFontGenerator = null; if (systemfont && Gdx.files.absolute("/system/fonts/Roboto-Regular.ttf").exists()) { basicFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/Roboto-Regular.ttf")); @@ -217,11 +197,15 @@ public class AndroidPlatformSupport extends PlatformSupport { case KOREAN: typeFace = 1; break; - case CHINESE: + case CHI_SMPL: default: typeFace = 2; + break; + case CHI_TRAD: + typeFace = 3; + break; } - KRFontGenerator = SCFontGenerator = JPFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansCJK-Regular.ttc"), typeFace); + KRFontGenerator = ZHFontGenerator = JPFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansCJK-Regular.ttc"), typeFace); //otherwise we have to go over a few possibilities. } else { @@ -235,10 +219,13 @@ public class AndroidPlatformSupport extends PlatformSupport { } //Chinese font generators + //we don't use a separate generator for traditional chinese because + // NotoSansTC-Regular and NotoSansHant-Regular seem to only contain some hant-specific + // ways to draw certain symbols, too much messing for old android if (Gdx.files.absolute("/system/fonts/NotoSansSC-Regular.otf").exists()){ - SCFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansSC-Regular.otf")); + ZHFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansSC-Regular.otf")); } else if (Gdx.files.absolute("/system/fonts/NotoSansHans-Regular.otf").exists()){ - SCFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansHans-Regular.otf")); + ZHFontGenerator = new FreeTypeFontGenerator(Gdx.files.absolute("/system/fonts/NotoSansHans-Regular.otf")); } //Japaneses font generators @@ -256,14 +243,14 @@ public class AndroidPlatformSupport extends PlatformSupport { } if (KRFontGenerator == null) KRFontGenerator = fallbackGenerator; - if (SCFontGenerator == null) SCFontGenerator = fallbackGenerator; + if (ZHFontGenerator == null) ZHFontGenerator = fallbackGenerator; if (JPFontGenerator == null) JPFontGenerator = fallbackGenerator; } if (basicFontGenerator != null) fonts.put(basicFontGenerator, new HashMap<>()); if (KRFontGenerator != null) fonts.put(KRFontGenerator, new HashMap<>()); - if (SCFontGenerator != null) fonts.put(SCFontGenerator, new HashMap<>()); + if (ZHFontGenerator != null) fonts.put(ZHFontGenerator, new HashMap<>()); if (JPFontGenerator != null) fonts.put(JPFontGenerator, new HashMap<>()); //would be nice to use RGBA4444 to save memory, but this causes problems on some gpus =S @@ -271,15 +258,15 @@ public class AndroidPlatformSupport extends PlatformSupport { } private static Matcher KRMatcher = Pattern.compile("\\p{InHangul_Syllables}").matcher(""); - private static Matcher SCMatcher = Pattern.compile("\\p{InCJK_Unified_Ideographs}|\\p{InCJK_Symbols_and_Punctuation}|\\p{InHalfwidth_and_Fullwidth_Forms}").matcher(""); + private static Matcher ZHMatcher = Pattern.compile("\\p{InCJK_Unified_Ideographs}|\\p{InCJK_Symbols_and_Punctuation}|\\p{InHalfwidth_and_Fullwidth_Forms}").matcher(""); private static Matcher JPMatcher = Pattern.compile("\\p{InHiragana}|\\p{InKatakana}").matcher(""); @Override protected FreeTypeFontGenerator getGeneratorForString( String input ){ if (KRMatcher.reset(input).find()){ return KRFontGenerator; - } else if (SCMatcher.reset(input).find()){ - return SCFontGenerator; + } else if (ZHMatcher.reset(input).find()){ + return ZHFontGenerator; } else if (JPMatcher.reset(input).find()){ return JPFontGenerator; } else { diff --git a/android/src/main/res/values-v28/style.xml b/android/src/main/res/values-v28/style.xml new file mode 100644 index 000000000..935b8d344 --- /dev/null +++ b/android/src/main/res/values-v28/style.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/android/src/main/res/values/style.xml b/android/src/main/res/values/style.xml new file mode 100644 index 000000000..249a143f0 --- /dev/null +++ b/android/src/main/res/values/style.xml @@ -0,0 +1,7 @@ + + + + +