Более менее стабильная версия

This commit is contained in:
2025-08-28 12:49:40 +03:00
parent 3cc802a9ea
commit 8c0276c562
16 changed files with 1228 additions and 1404 deletions
+8
View File
@@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-08-28T09:42:35.298333191Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="Default" identifier="serial=192.168.240.112:5555;connection=1b67fee5" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+1 -1
View File
@@ -26,7 +26,7 @@
<uses-configuration android:reqTouchScreen="finger"/> <uses-configuration android:reqTouchScreen="finger"/>
<application <application
android:theme="@style/Theme.AppCompat.Light" android:theme="@style/Theme.AppCompat.NoActionBar"
android:label="@string/app_name" android:label="@string/app_name"
android:icon="@mipmap/adaptive_icon" android:icon="@mipmap/adaptive_icon"
android:screenOrientation="userLandscape" android:screenOrientation="userLandscape"
+20 -11
View File
@@ -1,16 +1,25 @@
package com.ea.EAIO; package com.ea.EAIO
import android.app.Activity; import android.app.Activity
import android.content.res.AssetManager; import android.content.res.AssetManager
import android.os.Environment; import android.os.Environment
object EAIO {
@JvmStatic
external fun Shutdown()
public class EAIO { private external fun StartupNativeImpl(
public static native void Shutdown(); assetManager: AssetManager?,
dataPath: String?,
filesDirPath: String?,
externalPath: String?
)
private static native void StartupNativeImpl(AssetManager assetManager, String str, String str2, String str3); @JvmStatic
fun Startup(activity: Activity) = StartupNativeImpl(
public static void Startup(Activity activity) { activity.assets,
StartupNativeImpl(activity.getAssets(), Environment.getDataDirectory().getAbsolutePath(), activity.getFilesDir().getAbsolutePath(), Environment.getExternalStorageDirectory().getAbsolutePath()); Environment.getDataDirectory().absolutePath,
} activity.filesDir.absolutePath,
Environment.getExternalStorageDirectory().absolutePath
)
} }
@@ -1,42 +1,52 @@
package com.ea.EAMIO; package com.ea.EAMIO
import android.app.Activity; import android.annotation.SuppressLint
import android.os.Environment; import android.app.Activity
import android.os.Environment
//На методы есть ссылка из нативного кода но они не вызываются и не несут полезной нагрузки
@SuppressLint("StaticFieldLeak")
object StorageDirectory {
@JvmStatic
var sActivity: Activity? = null
public class StorageDirectory { private external fun ShutdownNativeImpl()
public static Activity sActivity;
private static native void ShutdownNativeImpl(); private external fun StartupNativeImpl()
private static native void StartupNativeImpl(); @JvmStatic
fun Startup(activity: Activity) {
public static void Startup(Activity activity) { sActivity = activity
sActivity = activity; StartupNativeImpl()
StartupNativeImpl();
} }
public static void Shutdown() { @JvmStatic
ShutdownNativeImpl(); fun Shutdown() {
ShutdownNativeImpl()
} }
public static String GetInternalStorageDirectory() { @JvmStatic
return sActivity.getFilesDir().getAbsolutePath(); fun GetInternalStorageDirectory(): String {
return sActivity?.filesDir?.absolutePath ?: ""
} }
public static String GetPrimaryExternalStorageDirectory() { @JvmStatic
return sActivity.getExternalFilesDir(null).getAbsolutePath(); fun GetPrimaryExternalStorageDirectory(): String {
return sActivity?.getExternalFilesDir(null)?.absolutePath ?: ""
} }
public static String GetPrimaryExternalStorageDirectoryRoot() { @JvmStatic
return Environment.getExternalStorageDirectory().getAbsolutePath(); fun GetPrimaryExternalStorageDirectoryRoot(): String {
return Environment.getExternalStorageDirectory().absolutePath
} }
public static int GetPrimaryExternalStorageState() { @JvmStatic
String externalStorageState = Environment.getExternalStorageState(); fun GetPrimaryExternalStorageState(): Int {
if ("mounted".equals(externalStorageState)) { val externalStorageState = Environment.getExternalStorageState()
return 2; return when {
Environment.MEDIA_MOUNTED == externalStorageState -> 2
Environment.MEDIA_MOUNTED_READ_ONLY == externalStorageState -> 1
else -> 0
} }
return "mounted_ro".equals(externalStorageState) ? 1 : 0;
} }
} }
@@ -1,64 +1,58 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.graphics.Bitmap; import android.content.res.AssetManager
import android.graphics.Canvas; import android.graphics.Canvas
import android.graphics.Paint; import android.graphics.Paint
import android.graphics.PorterDuff; import android.graphics.PorterDuff
import android.graphics.Typeface; import android.graphics.Typeface
import android.util.Log; import androidx.core.graphics.createBitmap
import com.ea.ironmonkey.GameActivityMain.Companion.mAssetLocationType
import com.ea.ironmonkey.domain.AssetLocationType
import nfs.mod.mpcore.MultiplayerCore; class BitmapGraphics(width: Int, height: Int) {
val bitmap = createBitmap(width, height)
val canvas = Canvas()
init {
public class BitmapGraphics { canvas.setBitmap(bitmap)
private Bitmap bitmap;
private Canvas canvas = new Canvas();
public BitmapGraphics(int i, int i2) {
this.bitmap = Bitmap.createBitmap(i, i2, Bitmap.Config.ARGB_8888);
this.canvas.setBitmap(this.bitmap);
} }
public Bitmap getBitmap() { fun clear() {
return this.bitmap; canvas.drawColor(0, PorterDuff.Mode.CLEAR)
} }
public Canvas getCanvas() { fun drawString(paint: Paint, text: String, x: Int, y: Int) = canvas.drawText(text, x.toFloat(), y.toFloat(), paint)
return this.canvas;
}
public void clear() { companion object {
this.canvas.drawColor(0, PorterDuff.Mode.CLEAR); private fun createPaint(typeface: Typeface?, f: Float): Paint {
} val paint = Paint()
paint.setTypeface(typeface)
paint.textSize = f
paint.setColor(-1)
paint.isAntiAlias = true
return paint
}
private static Paint createPaint(Typeface typeface, float f) { @JvmStatic
Paint paint = new Paint(); fun createPaintFromFamilyName(familyName: String, f: Float): Paint {
paint.setTypeface(typeface); return createPaint(Typeface.create(familyName, Typeface.NORMAL), f)
paint.setTextSize(f); }
paint.setColor(-1);
paint.setAntiAlias(true);
return paint;
}
public static Paint createPaintFromFamilyName(String str, float f) { @JvmStatic
return createPaint(Typeface.create(str, 0), f); fun createPaintFromFile(str: String, f: Float): Paint {
} return createPaint(internalCreateFromFile(str), f)
}
public static Paint createPaintFromFile(String str, float f) { private fun internalCreateFromFile(str: String): Typeface? {
return createPaint(internalCreateFromFile(str), f); var str = str
} /*if (mAssetLocationType != AssetLocationType.EXTERNAL) {
return Typeface.createFromFile(str);
private static Typeface internalCreateFromFile(String str) { }*/
if (GameActivityMain.instance.useAssetsFileSystem()) {
if (str.startsWith("/")) { if (str.startsWith("/")) {
str = str.substring(1); str = str.substring(1);
} }
return Typeface.createFromAsset(GameActivityMain.instance.getAssetManager(), str); return Typeface.createFromAsset(GameActivityMain.assetManager, str);
}
return Typeface.createFromFile(str);
}
public void drawString(Paint paint, String str, int x, int y) { }
this.canvas.drawText(str, x, y, paint);
} }
} }
@@ -1,8 +1,7 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL10
interface DrawFrameListener {
public interface DrawFrameListener { fun onDrawFrame(gl10: GL10?)
void onDrawFrame(GL10 gl10);
} }
File diff suppressed because it is too large Load Diff
+20 -36
View File
@@ -1,46 +1,30 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.util.Log
public class Log { object Log {
private static boolean enable; private var enable = false
public static void setEnable(boolean z) { fun setEnable(enabled: Boolean) {
enable = z; enable = enabled
} }
public static void i(String str, String str2) { @JvmStatic
if (enable) { fun i(tag: String, msg: String) = if (enable) Log.i(tag, msg) else Unit
android.util.Log.i(str, str2);
}
}
public static void e(String str, String str2) { @JvmStatic
if (enable) { fun e(tag: String, msg: String) = if (enable) Log.e(tag, msg) else Unit
android.util.Log.e(str, str2);
}
}
public static void w(String str, String str2) { @JvmStatic
if (enable) { fun w(tag: String, msg: String) = if (enable) Log.w(tag, msg) else Unit
android.util.Log.w(str, str2);
}
}
public static void d(String str, String str2) { @JvmStatic
if (enable) { fun d(tag: String, msg: String) = if (enable) Log.d(tag, msg) else Unit
android.util.Log.d(str, str2);
}
}
public static void v(String str, String str2) { @JvmStatic
if (enable) { fun v(tag: String, msg: String) = if (enable) Log.v(tag, msg) else Unit
android.util.Log.v(str, str2);
}
}
public static void e(String str, String str2, Exception exc) { @JvmStatic
if (enable) { fun e(tag: String, msg: String?, exc: Exception?) = if (enable) Log.e(tag, msg, exc) else Unit
android.util.Log.e(str, str2, exc); }
}
}
}
@@ -1,30 +1,117 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import com.bda.controller.ControllerListener; import android.content.Context
import com.bda.controller.KeyEvent; import android.hardware.input.InputManager
import com.bda.controller.MotionEvent; import android.util.Log
import com.bda.controller.StateEvent; import android.view.InputDevice
import androidx.activity.ComponentActivity
import com.bda.controller.ControllerListener
import com.bda.controller.KeyEvent
import com.bda.controller.MotionEvent
import com.bda.controller.StateEvent
object MogaController : ControllerListener {
external fun nativeOnKeyEvent(keyEvent: KeyEvent?)
public class MogaController implements ControllerListener { external fun nativeOnMotionEvent(motionEvent: MotionEvent?)
native void nativeOnKeyEvent(KeyEvent keyEvent);
native void nativeOnMotionEvent(MotionEvent motionEvent); external fun nativeOnStateEvent(stateEvent: StateEvent?)
native void nativeOnStateEvent(StateEvent stateEvent); override fun onKeyEvent(keyEvent: KeyEvent?) {
nativeOnKeyEvent(keyEvent)
@Override // com.bda.controller.ControllerListener
public void onKeyEvent(KeyEvent keyEvent) {
nativeOnKeyEvent(keyEvent);
} }
@Override // com.bda.controller.ControllerListener override fun onMotionEvent(motionEvent: MotionEvent?) {
public void onMotionEvent(MotionEvent motionEvent) { nativeOnMotionEvent(motionEvent)
nativeOnMotionEvent(motionEvent);
} }
@Override // com.bda.controller.ControllerListener override fun onStateEvent(stateEvent: StateEvent?) {
public void onStateEvent(StateEvent stateEvent) { nativeOnStateEvent(stateEvent)
nativeOnStateEvent(stateEvent);
} }
fun doForAllControllers(
action: (Int) -> Unit
){
val deviceIds = InputDevice.getDeviceIds()
deviceIds.forEach {
if(deviceIsController(it)) action(it)
}
}
infix fun Int.isFlag(flag: Int) = this and flag == flag
fun deviceIsController(deviceId: Int): Boolean {
val inputDevice = InputDevice.getDevice(deviceId) ?: return false
if(inputDevice.id == -1) return false
return (inputDevice.sources isFlag InputDevice.SOURCE_GAMEPAD) &&
(inputDevice.sources isFlag InputDevice.SOURCE_JOYSTICK)
}
fun registerControllerHandlers(
context: Context,
onInputDeviceAdded: (Int) -> Unit,
onInputDeviceRemoved: (Int) -> Unit,
onInputDeviceChanged: (Int) -> Unit = {},
){
val inputManager = context.getSystemService(ComponentActivity.INPUT_SERVICE) as InputManager
inputManager.registerInputDeviceListener(object : InputManager.InputDeviceListener {
var lastControllerId: Int = 0;
override fun onInputDeviceAdded(deviceId: Int) {
if(deviceIsController(deviceId)) {
lastControllerId = deviceId
onInputDeviceAdded(deviceId)
}
}
override fun onInputDeviceRemoved(deviceId: Int) {
if(deviceIsController(deviceId) || lastControllerId == deviceId) onInputDeviceRemoved(deviceId)
}
override fun onInputDeviceChanged(deviceId: Int) {
if(deviceIsController(deviceId)) onInputDeviceChanged(deviceId)
}
}, null)
}
fun createDisconnectEvent(deviceId: Int) = StateEvent(
System.currentTimeMillis(),
deviceId,
StateEvent.STATE_UNKNOWN,
StateEvent.ACTION_DISCONNECTED
)
fun createConnectEvent(deviceId: Int) = StateEvent(
System.currentTimeMillis(),
deviceId,
StateEvent.STATE_CONNECTION,
StateEvent.ACTION_CONNECTED
)
fun androidKeyEventToBda(keyEvent: android.view.KeyEvent) = KeyEvent(
keyEvent.eventTime,
keyEvent.deviceId,
keyEvent.keyCode,
keyEvent.action
)
fun androidMotionEventToBda(motionEvent: android.view.MotionEvent) = MotionEvent(
motionEvent.eventTime,
motionEvent.deviceId,
motionEvent.x,
motionEvent.y,
motionEvent.rawX,
motionEvent.rawY,
motionEvent.xPrecision,
motionEvent.yPrecision
)
} }
@@ -1,13 +1,8 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.content.Context; import android.content.Context
internal object ObbHelper {
class ObbHelper { @JvmStatic
ObbHelper() { fun getObbFileName(context: Context, versionCode: Int) = "main.$versionCode.${context.packageName}.obb"
}
public static String getObbFileName(Context context, int i) {
return "main." + i + "." + context.getPackageName() + ".obb";
}
} }
@@ -1,182 +1,186 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.KeyEvent
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import android.Manifest; class PermissionsActivity : AppCompatActivity() {
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import androidx.appcompat.app.AppCompatActivity; companion object {
import androidx.core.app.ActivityCompat; private const val SETTINGS_REQUEST_CODE = 100
import androidx.core.content.ContextCompat;
import android.os.Build;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import java.util.ArrayList;
import java.util.List;
public class PermissionsActivity extends AppCompatActivity {
private static final int SETTINGS_REQUEST_CODE = 100;
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> {
if (areAllPermissionsGranted()) {
initActivity();
} else {
handlePermissionsDenied();
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkPermissions();
} }
private void checkPermissions() { private val requestPermissionLauncher: ActivityResultLauncher<Array<String>> =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
if (areAllPermissionsGranted()) {
initActivity()
} else {
handlePermissionsDenied()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkPermissions()
}
private fun checkPermissions() {
if (areAllPermissionsGranted()) { if (areAllPermissionsGranted()) {
initActivity(); initActivity()
} else { } else {
requestPermissions(); requestPermissions()
} }
} }
private boolean areAllPermissionsGranted() { private fun areAllPermissionsGranted(): Boolean {
for (String permission : getRequiredPermissions()) { return getRequiredPermissions().all { permission ->
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
return false;
}
} }
return true;
} }
private String[] getRequiredPermissions() { private fun getRequiredPermissions(): Array<String> {
List<String> permissions = new ArrayList<>(); val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { when {
// Android 9 (Pie) и ниже - нужны оба разрешения Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> {
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); // Android 9 (Pie) и ниже - нужны оба разрешения
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
} else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) { permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
// Android 10 (Q) - только чтение }
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> {
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 10 (Q) - только чтение
// Android 13+ - новые раздельные разрешения для медиа permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
//permissions.add(Manifest.permission.READ_MEDIA_IMAGES); }
//permissions.add(Manifest.permission.READ_MEDIA_VIDEO); Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 13+ - новые раздельные разрешения для медиа
// Android 14+ - дополнительно для аудио // permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
permissions.add(Manifest.permission.READ_MEDIA_AUDIO); // permissions.add(Manifest.permission.READ_MEDIA_VIDEO)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Android 14+ - дополнительно для аудио
//permissions.add(Manifest.permission.READ_MEDIA_AUDIO)
}
}
else -> {
// Android 11-12 - только чтение
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
} }
} else {
// Android 11-12 - только чтение
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
} }
permissions.forEach(e -> { permissions.forEach { permission ->
Log.i("Permissions", e); Log.i("Permissions", permission)
}); }
return permissions.toArray(new String[0]); return permissions.toTypedArray()
} }
private void requestPermissions() { private fun requestPermissions() {
String[] permissions = getRequiredPermissions(); val permissions = getRequiredPermissions()
boolean shouldShowRationale = false; val shouldShowRationale = permissions.any { permission ->
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
shouldShowRationale = true;
break;
}
} }
if (shouldShowRationale) { if (shouldShowRationale) {
showPermissionRationaleDialog(); showPermissionRationaleDialog()
} else { } else {
requestPermissionLauncher.launch(permissions); requestPermissionLauncher.launch(permissions)
} }
} }
private void showPermissionRationaleDialog() { private fun showPermissionRationaleDialog() {
new AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("Требуются разрешения") .setTitle("Требуются разрешения")
.setMessage("Для корректной работы приложения необходимы разрешения на доступ к хранилищу. Пожалуйста, предоставьте запрашиваемые разрешения.") .setMessage("Для корректной работы приложения необходимы разрешения на доступ к хранилищу. Пожалуйста, предоставьте запрашиваемые разрешения.")
.setPositiveButton("OK", (dialog, which) -> requestPermissionLauncher.launch(getRequiredPermissions())) .setPositiveButton("OK") { dialog, _ ->
.setNegativeButton("Отмена", (dialog, which) -> finish()) dialog.dismiss()
.setCancelable(false) requestPermissionLauncher.launch(getRequiredPermissions())
.setOnKeyListener((dialog, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
return true;
}
return false;
})
.show();
}
private void handlePermissionsDenied() {
if (areSomePermissionsPermanentlyDenied()) {
showPermanentlyDeniedDialog();
} else {
Toast.makeText(this, "Разрешения необходимы для работы приложения", Toast.LENGTH_SHORT).show();
finish();
}
}
private boolean areSomePermissionsPermanentlyDenied() {
for (String permission : getRequiredPermissions()) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission) &&
ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return true;
} }
.setNegativeButton("Отмена") { dialog, _ ->
dialog.dismiss()
finish()
}
.setCancelable(false)
.setOnKeyListener { _, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish()
true
} else {
false
}
}
.show()
}
private fun handlePermissionsDenied() {
if (areSomePermissionsPermanentlyDenied()) {
showPermanentlyDeniedDialog()
} else {
Toast.makeText(this, "Разрешения необходимы для работы приложения", Toast.LENGTH_SHORT).show()
finish()
} }
return false;
} }
private void showPermanentlyDeniedDialog() { private fun areSomePermissionsPermanentlyDenied(): Boolean {
new AlertDialog.Builder(this) return getRequiredPermissions().any { permission ->
.setTitle("Разрешения отклонены") !ActivityCompat.shouldShowRequestPermissionRationale(this, permission) &&
.setMessage("Вы навсегда отклонили некоторые разрешения. Чтобы использовать приложение, предоставьте разрешения в настройках.") ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
.setPositiveButton("Настройки", (dialog, which) -> openAppSettings()) }
.setNegativeButton("Выход", (dialog, which) -> finish())
.setCancelable(false)
.show();
} }
private void openAppSettings() { private fun showPermanentlyDeniedDialog() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); AlertDialog.Builder(this)
intent.setData(Uri.parse("package:" + getPackageName())); .setTitle("Разрешения отклонены")
startActivityForResult(intent, SETTINGS_REQUEST_CODE); .setMessage("Вы навсегда отклонили некоторые разрешения. Чтобы использовать приложение, предоставьте разрешения в настройках.")
.setPositiveButton("Настройки") { dialog, _ ->
dialog.dismiss()
openAppSettings()
}
.setNegativeButton("Выход") { dialog, _ ->
dialog.dismiss()
finish()
}
.setCancelable(false)
.show()
} }
@Override private fun openAppSettings() {
protected void onActivityResult(int requestCode, int resultCode, Intent data) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
super.onActivityResult(requestCode, resultCode, data); data = Uri.fromParts("package", packageName, null)
}
startActivityForResult(intent, SETTINGS_REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SETTINGS_REQUEST_CODE) { if (requestCode == SETTINGS_REQUEST_CODE) {
if (areAllPermissionsGranted()) { if (areAllPermissionsGranted()) {
initActivity(); initActivity()
} else { } else {
handlePermissionsDenied(); handlePermissionsDenied()
} }
} }
} }
private void initActivity() { private fun initActivity() {
try { try {
startActivity(new Intent(this, GameActivityMain.class)); startActivity(Intent(this, GameActivityMain::class.java))
finish(); finish()
} catch (Exception e) { } catch (e: Exception) {
e.printStackTrace(); e.printStackTrace()
Toast.makeText(this, "Ошибка запуска приложения", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Ошибка запуска приложения", Toast.LENGTH_SHORT).show()
finish(); finish()
} }
} }
} }
+33 -38
View File
@@ -1,57 +1,52 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.opengl.GLSurfaceView;
public class RunLoop { import android.opengl.GLSurfaceView
public static final int STATE_RUNNING = 1; import android.util.Log
public static final int STATE_STOPPED = 0;
private GLSurfaceView glSurfaceView;
private int state;
private native void nativeOnRunLoopTick(); class RunLoop(private val glSurfaceView: GLSurfaceView) {
public RunLoop(GLSurfaceView gLSurfaceView) { companion object {
this.glSurfaceView = gLSurfaceView; private const val TAG = "RunLoop"
updateRenderMode(); private const val STATE_STOPPED = 0
private const val STATE_RUNNING = 1
} }
public int getState() { private var state: Int = STATE_STOPPED
return this.state;
private external fun nativeOnRunLoopTick()
val currentState: Int
get() = state
fun start() {
setState(STATE_RUNNING)
} }
public void start() { fun stop() {
setState(1); setState(STATE_STOPPED)
} }
public void stop() { fun join() {
setState(0); setState(STATE_STOPPED)
} }
public void join() { private fun setState(newState: Int) {
setState(0); state = newState
updateRenderMode()
} }
private void setState(int i) { private fun updateRenderMode() {
this.state = i; Log.v(TAG, "RunLoop.state = $state")
updateRenderMode(); when (state) {
} STATE_STOPPED -> glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
STATE_RUNNING -> glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
private void updateRenderMode() {
Log.v("RunLoop", "RunLoop.state = " + this.state);
switch (this.state) {
case 0:
this.glSurfaceView.setRenderMode(0);
break;
case 1:
this.glSurfaceView.setRenderMode(1);
break;
} }
} }
public void onRunLoopTick() { fun onRunLoopTick() {
if (this.state == 1) { if (state == STATE_RUNNING) {
nativeOnRunLoopTick(); nativeOnRunLoopTick()
} }
} }
} }
@@ -1,255 +1,258 @@
package com.ea.ironmonkey; package com.ea.ironmonkey
import android.app.Activity; import android.app.Activity
import android.graphics.Bitmap; import android.graphics.BitmapFactory
import android.graphics.BitmapFactory; import android.opengl.GLES20
import android.opengl.GLES20; import android.opengl.GLUtils
import android.opengl.GLUtils; import android.util.Log
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.opengles.GL10
import java.io.IOException; class SplashScreen(private val activity: Activity) {
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
companion object {
public class SplashScreen { private const val TAG = "SplashScreen"
private static final String TAG = "SplashScreen";
private Activity _activity;
private int _attPosition;
private int _attSampler;
private int _attTexCoord;
private int _fragmentShader;
private int _program;
private int _vertexShader;
private FloatBuffer vBuffer;
// Вершинный шейдер
private final String vShaderStr =
"attribute vec4 a_position; \n" +
"attribute vec2 a_texCoord; \n" +
"varying vec2 v_texCoord; \n" +
"void main() { \n" +
" gl_Position = a_position; \n" +
" v_texCoord = a_texCoord; \n" +
"} \n";
// Фрагментный шейдер
private final String fShaderStr =
"precision mediump float; \n" +
"varying vec2 v_texCoord; \n" +
"uniform sampler2D s_texture; \n" +
"void main() { \n" +
" gl_FragColor = texture2D(s_texture, v_texCoord); \n" +
"} \n";
private int[] _textureId = new int[1];
public SplashScreen(Activity activity) {
this._activity = activity;
} }
public void init(GL10 gl10, int width, int height) { // Вершинный шейдер
private val vertexShaderStr = """
attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = a_position;
v_texCoord = a_texCoord;
}
""".trimIndent()
// Фрагментный шейдер
private val fragmentShaderStr = """
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D s_texture;
void main() {
gl_FragColor = texture2D(s_texture, v_texCoord);
}
""".trimIndent()
private var attPosition = 0
private var attSampler = 0
private var attTexCoord = 0
private var fragmentShader = 0
private var program = 0
private var vertexShader = 0
private var vBuffer: FloatBuffer? = null
private var textureId = IntArray(1)
fun init(gl10: GL10?, width: Int, height: Int) {
Log.i("splash", "Я родился!")
if (!initRenderer()) { if (!initRenderer()) {
destroy(gl10); destroy(gl10)
return; return
} }
// Загрузка текстуры // Загрузка текстуры
GLES20.glGenTextures(1, _textureId, 0); GLES20.glGenTextures(1, textureId, 0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _textureId[0]); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0])
// Установка параметров текстуры // Установка параметров текстуры
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
// Загрузка изображения // Загрузка изображения
Bitmap bitmap = null; val bitmap = try {
try { activity.assets.open("splash.png").use { stream ->
InputStream is = _activity.getAssets().open("splash.png"); BitmapFactory.decodeStream(stream)
bitmap = BitmapFactory.decodeStream(is); }
} catch (IOException e) { } catch (e: IOException) {
Log.e(TAG, "Could not load bitmap", e); Log.e(TAG, "Could not load bitmap", e)
null
} }
if (bitmap != null) { if (bitmap != null) {
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
bitmap.recycle(); bitmap.recycle()
} else { } else {
Log.e(TAG, "Failed to load bitmap"); Log.e(TAG, "Failed to load bitmap")
destroy(gl10); destroy(gl10)
return; return
} }
// Проверка ошибок // Проверка ошибок
int error = GLES20.glGetError(); val error = GLES20.glGetError()
if (error != GLES20.GL_NO_ERROR) { if (error != GLES20.GL_NO_ERROR) {
Log.e(TAG, "Texture Load GLError: " + error); Log.e(TAG, "Texture Load GLError: $error")
destroy(gl10); destroy(gl10)
} }
} }
private boolean initRenderer() { private fun initRenderer(): Boolean {
// Загрузка шейдеров // Загрузка шейдеров
_vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vShaderStr); vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderStr)
_fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fShaderStr); fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderStr)
// Создание программы // Создание программы
_program = GLES20.glCreateProgram(); program = GLES20.glCreateProgram()
if (_program == 0) { if (program == 0) {
Log.e(TAG, "Failed to create program"); Log.e(TAG, "Failed to create program")
return false; return false
} }
// Прикрепление шейдеров // Прикрепление шейдеров
GLES20.glAttachShader(_program, _vertexShader); GLES20.glAttachShader(program, vertexShader)
GLES20.glAttachShader(_program, _fragmentShader); GLES20.glAttachShader(program, fragmentShader)
// Линковка программы // Линковка программы
GLES20.glLinkProgram(_program); GLES20.glLinkProgram(program)
// Проверка статуса линковки // Проверка статуса линковки
int[] linkStatus = new int[1]; val linkStatus = IntArray(1)
GLES20.glGetProgramiv(_program, GLES20.GL_LINK_STATUS, linkStatus, 0); GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES20.GL_TRUE) { if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: " + GLES20.glGetProgramInfoLog(_program)); Log.e(TAG, "Could not link program: ${GLES20.glGetProgramInfoLog(program)}")
GLES20.glDeleteProgram(_program); GLES20.glDeleteProgram(program)
_program = 0; program = 0
return false; return false
} }
// Получение атрибутов // Получение атрибутов
_attPosition = GLES20.glGetAttribLocation(_program, "a_position"); attPosition = GLES20.glGetAttribLocation(program, "a_position")
_attTexCoord = GLES20.glGetAttribLocation(_program, "a_texCoord"); attTexCoord = GLES20.glGetAttribLocation(program, "a_texCoord")
_attSampler = GLES20.glGetUniformLocation(_program, "s_texture"); attSampler = GLES20.glGetUniformLocation(program, "s_texture")
return true; return true
} }
private int loadShader(int type, String shaderCode) { private fun loadShader(type: Int, shaderCode: String): Int {
int shader = GLES20.glCreateShader(type); val shader = GLES20.glCreateShader(type)
if (shader == 0) { if (shader == 0) {
Log.e(TAG, "Failed to create shader"); Log.e(TAG, "Failed to create shader")
return 0; return 0
} }
GLES20.glShaderSource(shader, shaderCode); GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader); GLES20.glCompileShader(shader)
// Проверка статуса компиляции // Проверка статуса компиляции
int[] compiled = new int[1]; val compiled = IntArray(1)
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == 0) { if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader: " + GLES20.glGetShaderInfoLog(shader)); Log.e(TAG, "Could not compile shader: ${GLES20.glGetShaderInfoLog(shader)}")
GLES20.glDeleteShader(shader); GLES20.glDeleteShader(shader)
return 0; return 0
} }
return shader; return shader
} }
public boolean draw(GL10 gl10, int width, int height) { fun draw(gl10: GL10?, width: Int, height: Int): Boolean {
if (_textureId[0] == 0 || _program == 0) { if (textureId[0] == 0 || program == 0) {
return false; return false
} }
// Расчет координат с уменьшением на 20% // Расчет координат с уменьшением на 20%
float ratio = (float) width / height; val ratio = width.toFloat() / height
float imageRatio = 1.0f; // Предполагаем квадратное изображение val imageRatio = 1.0f // Предполагаем квадратное изображение
float scaleX, scaleY; val (scaleX, scaleY) = if (ratio > imageRatio) {
if (ratio > imageRatio) {
// Шире, чем изображение // Шире, чем изображение
scaleY = 0.8f; // Уменьшаем на 20% val scaleY = 0.8f // Уменьшаем на 20%
scaleX = imageRatio / ratio * 0.8f; val scaleX = imageRatio / ratio * 0.8f
scaleX to scaleY
} else { } else {
// Уже, чем изображение // Уже, чем изображение
scaleX = 0.8f; // Уменьшаем на 20% val scaleX = 0.8f // Уменьшаем на 20%
scaleY = ratio / imageRatio * 0.8f; val scaleY = ratio / imageRatio * 0.8f
scaleX to scaleY
} }
// Координаты вершин и текстур // Координаты вершин и текстур
float[] vertices = { val vertices = floatArrayOf(
-scaleX, -scaleY, 0.0f, // нижний левый -scaleX, -scaleY, 0.0f, // нижний левый
scaleX, -scaleY, 0.0f, // нижний правый scaleX, -scaleY, 0.0f, // нижний правый
-scaleX, scaleY, 0.0f, // верхний левый -scaleX, scaleY, 0.0f, // верхний левый
scaleX, scaleY, 0.0f // верхний правый scaleX, scaleY, 0.0f // верхний правый
}; )
float[] texCoords = { val texCoords = floatArrayOf(
0.0f, 1.0f, // нижний левый 0.0f, 1.0f, // нижний левый
1.0f, 1.0f, // нижний правый 1.0f, 1.0f, // нижний правый
0.0f, 0.0f, // верхний левый 0.0f, 0.0f, // верхний левый
1.0f, 0.0f // верхний правый 1.0f, 0.0f // верхний правый
}; )
// Создание буферов // Создание буферов
ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4); val vertexBuffer = ByteBuffer
bb.order(ByteOrder.nativeOrder()); .allocateDirect(vertices.size * 4)
FloatBuffer vertexBuffer = bb.asFloatBuffer(); .order(ByteOrder.nativeOrder())
vertexBuffer.put(vertices); .asFloatBuffer()
vertexBuffer.position(0); .apply {
put(vertices)
position(0)
}
bb = ByteBuffer.allocateDirect(texCoords.length * 4); val texBuffer = ByteBuffer
bb.order(ByteOrder.nativeOrder()); .allocateDirect(texCoords.size * 4)
FloatBuffer texBuffer = bb.asFloatBuffer(); .order(ByteOrder.nativeOrder())
texBuffer.put(texCoords); .asFloatBuffer()
texBuffer.position(0); .apply {
put(texCoords)
position(0)
}
// Отрисовка с белым фоном // Отрисовка с белым фоном
GLES20.glViewport(0, 0, width, height); GLES20.glViewport(0, 0, width, height)
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Белый цвет GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) // Белый цвет
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
GLES20.glUseProgram(_program); GLES20.glUseProgram(program)
// Передача вершин // Передача вершин
GLES20.glVertexAttribPointer(_attPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer); GLES20.glVertexAttribPointer(attPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)
GLES20.glEnableVertexAttribArray(_attPosition); GLES20.glEnableVertexAttribArray(attPosition)
// Передача текстурных координат // Передача текстурных координат
GLES20.glVertexAttribPointer(_attTexCoord, 2, GLES20.GL_FLOAT, false, 0, texBuffer); GLES20.glVertexAttribPointer(attTexCoord, 2, GLES20.GL_FLOAT, false, 0, texBuffer)
GLES20.glEnableVertexAttribArray(_attTexCoord); GLES20.glEnableVertexAttribArray(attTexCoord)
// Активация текстуры // Активация текстуры
GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _textureId[0]); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0])
GLES20.glUniform1i(_attSampler, 0); GLES20.glUniform1i(attSampler, 0)
// Отрисовка // Отрисовка
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
// Отключение атрибутов // Отключение атрибутов
GLES20.glDisableVertexAttribArray(_attPosition); GLES20.glDisableVertexAttribArray(attPosition)
GLES20.glDisableVertexAttribArray(_attTexCoord); GLES20.glDisableVertexAttribArray(attTexCoord)
return true; return true
} }
public void destroy(GL10 gl10) { fun destroy(gl10: GL10?) {
if (_textureId[0] != 0) { if (textureId[0] != 0) {
GLES20.glDeleteTextures(1, _textureId, 0); GLES20.glDeleteTextures(1, textureId, 0)
_textureId[0] = 0; textureId[0] = 0
} }
if (_program != 0) { if (program != 0) {
GLES20.glDeleteProgram(_program); GLES20.glDeleteProgram(program)
_program = 0; program = 0
} }
if (_vertexShader != 0) { if (vertexShader != 0) {
GLES20.glDeleteShader(_vertexShader); GLES20.glDeleteShader(vertexShader)
_vertexShader = 0; vertexShader = 0
} }
if (_fragmentShader != 0) { if (fragmentShader != 0) {
GLES20.glDeleteShader(_fragmentShader); GLES20.glDeleteShader(fragmentShader)
_fragmentShader = 0; fragmentShader = 0
}
if (vBuffer != null) {
vBuffer.clear();
vBuffer = null;
} }
vBuffer?.clear()
vBuffer = null
} }
} }
@@ -0,0 +1,7 @@
package com.ea.ironmonkey.domain
enum class AssetLocationType {
EXTERNAL,
ASSETS,
OBB
}
@@ -0,0 +1,11 @@
package com.ea.ironmonkey.domain
import com.google.gson.annotations.SerializedName
class FSNode {
@SerializedName("directory")
var directory: Boolean = false
@SerializedName("size")
var size: Int = 0
}