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

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>
<SelectionState runConfigName="app">
<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>
</selectionStates>
</component>
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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" />
</component>
<component name="ProjectType">
+1 -1
View File
@@ -26,7 +26,7 @@
<uses-configuration android:reqTouchScreen="finger"/>
<application
android:theme="@style/Theme.AppCompat.Light"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:label="@string/app_name"
android:icon="@mipmap/adaptive_icon"
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.content.res.AssetManager;
import android.os.Environment;
import android.app.Activity
import android.content.res.AssetManager
import android.os.Environment
object EAIO {
@JvmStatic
external fun Shutdown()
public class EAIO {
public static native void Shutdown();
private external fun StartupNativeImpl(
assetManager: AssetManager?,
dataPath: String?,
filesDirPath: String?,
externalPath: String?
)
private static native void StartupNativeImpl(AssetManager assetManager, String str, String str2, String str3);
public static void Startup(Activity activity) {
StartupNativeImpl(activity.getAssets(), Environment.getDataDirectory().getAbsolutePath(), activity.getFilesDir().getAbsolutePath(), Environment.getExternalStorageDirectory().getAbsolutePath());
}
@JvmStatic
fun Startup(activity: Activity) = StartupNativeImpl(
activity.assets,
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.os.Environment;
import android.annotation.SuppressLint
import android.app.Activity
import android.os.Environment
//На методы есть ссылка из нативного кода но они не вызываются и не несут полезной нагрузки
@SuppressLint("StaticFieldLeak")
object StorageDirectory {
@JvmStatic
var sActivity: Activity? = null
public class StorageDirectory {
public static Activity sActivity;
private external fun ShutdownNativeImpl()
private static native void ShutdownNativeImpl();
private external fun StartupNativeImpl()
private static native void StartupNativeImpl();
public static void Startup(Activity activity) {
sActivity = activity;
StartupNativeImpl();
@JvmStatic
fun Startup(activity: Activity) {
sActivity = activity
StartupNativeImpl()
}
public static void Shutdown() {
ShutdownNativeImpl();
@JvmStatic
fun Shutdown() {
ShutdownNativeImpl()
}
public static String GetInternalStorageDirectory() {
return sActivity.getFilesDir().getAbsolutePath();
@JvmStatic
fun GetInternalStorageDirectory(): String {
return sActivity?.filesDir?.absolutePath ?: ""
}
public static String GetPrimaryExternalStorageDirectory() {
return sActivity.getExternalFilesDir(null).getAbsolutePath();
@JvmStatic
fun GetPrimaryExternalStorageDirectory(): String {
return sActivity?.getExternalFilesDir(null)?.absolutePath ?: ""
}
public static String GetPrimaryExternalStorageDirectoryRoot() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
@JvmStatic
fun GetPrimaryExternalStorageDirectoryRoot(): String {
return Environment.getExternalStorageDirectory().absolutePath
}
public static int GetPrimaryExternalStorageState() {
String externalStorageState = Environment.getExternalStorageState();
if ("mounted".equals(externalStorageState)) {
return 2;
@JvmStatic
fun GetPrimaryExternalStorageState(): Int {
val externalStorageState = Environment.getExternalStorageState()
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.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.util.Log;
import android.content.res.AssetManager
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.Typeface
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()
public class BitmapGraphics {
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);
init {
canvas.setBitmap(bitmap)
}
public Bitmap getBitmap() {
return this.bitmap;
fun clear() {
canvas.drawColor(0, PorterDuff.Mode.CLEAR)
}
public Canvas getCanvas() {
return this.canvas;
}
fun drawString(paint: Paint, text: String, x: Int, y: Int) = canvas.drawText(text, x.toFloat(), y.toFloat(), paint)
public void clear() {
this.canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
companion object {
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) {
Paint paint = new Paint();
paint.setTypeface(typeface);
paint.setTextSize(f);
paint.setColor(-1);
paint.setAntiAlias(true);
return paint;
}
@JvmStatic
fun createPaintFromFamilyName(familyName: String, f: Float): Paint {
return createPaint(Typeface.create(familyName, Typeface.NORMAL), f)
}
public static Paint createPaintFromFamilyName(String str, float f) {
return createPaint(Typeface.create(str, 0), f);
}
@JvmStatic
fun createPaintFromFile(str: String, f: Float): Paint {
return createPaint(internalCreateFromFile(str), f)
}
public static Paint createPaintFromFile(String str, float f) {
return createPaint(internalCreateFromFile(str), f);
}
private static Typeface internalCreateFromFile(String str) {
if (GameActivityMain.instance.useAssetsFileSystem()) {
private fun internalCreateFromFile(str: String): Typeface? {
var str = str
/*if (mAssetLocationType != AssetLocationType.EXTERNAL) {
return Typeface.createFromFile(str);
}*/
if (str.startsWith("/")) {
str = str.substring(1);
}
return Typeface.createFromAsset(GameActivityMain.instance.getAssetManager(), str);
}
return Typeface.createFromFile(str);
}
return Typeface.createFromAsset(GameActivityMain.assetManager, 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
public interface DrawFrameListener {
void onDrawFrame(GL10 gl10);
interface DrawFrameListener {
fun onDrawFrame(gl10: GL10?)
}
File diff suppressed because it is too large Load Diff
+19 -35
View File
@@ -1,46 +1,30 @@
package com.ea.ironmonkey;
package com.ea.ironmonkey
import android.util.Log
public class Log {
private static boolean enable;
object Log {
private var enable = false
public static void setEnable(boolean z) {
enable = z;
fun setEnable(enabled: Boolean) {
enable = enabled
}
public static void i(String str, String str2) {
if (enable) {
android.util.Log.i(str, str2);
}
}
@JvmStatic
fun i(tag: String, msg: String) = if (enable) Log.i(tag, msg) else Unit
public static void e(String str, String str2) {
if (enable) {
android.util.Log.e(str, str2);
}
}
@JvmStatic
fun e(tag: String, msg: String) = if (enable) Log.e(tag, msg) else Unit
public static void w(String str, String str2) {
if (enable) {
android.util.Log.w(str, str2);
}
}
@JvmStatic
fun w(tag: String, msg: String) = if (enable) Log.w(tag, msg) else Unit
public static void d(String str, String str2) {
if (enable) {
android.util.Log.d(str, str2);
}
}
@JvmStatic
fun d(tag: String, msg: String) = if (enable) Log.d(tag, msg) else Unit
public static void v(String str, String str2) {
if (enable) {
android.util.Log.v(str, str2);
}
}
@JvmStatic
fun v(tag: String, msg: String) = if (enable) Log.v(tag, msg) else Unit
public static void e(String str, String str2, Exception exc) {
if (enable) {
android.util.Log.e(str, str2, exc);
}
}
@JvmStatic
fun e(tag: String, msg: String?, exc: Exception?) = if (enable) Log.e(tag, msg, exc) else Unit
}
@@ -1,30 +1,117 @@
package com.ea.ironmonkey;
package com.ea.ironmonkey
import com.bda.controller.ControllerListener;
import com.bda.controller.KeyEvent;
import com.bda.controller.MotionEvent;
import com.bda.controller.StateEvent;
import android.content.Context
import android.hardware.input.InputManager
import android.util.Log
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 {
native void nativeOnKeyEvent(KeyEvent keyEvent);
external fun nativeOnMotionEvent(motionEvent: MotionEvent?)
native void nativeOnMotionEvent(MotionEvent motionEvent);
external fun nativeOnStateEvent(stateEvent: StateEvent?)
native void nativeOnStateEvent(StateEvent stateEvent);
@Override // com.bda.controller.ControllerListener
public void onKeyEvent(KeyEvent keyEvent) {
nativeOnKeyEvent(keyEvent);
override fun onKeyEvent(keyEvent: KeyEvent?) {
nativeOnKeyEvent(keyEvent)
}
@Override // com.bda.controller.ControllerListener
public void onMotionEvent(MotionEvent motionEvent) {
nativeOnMotionEvent(motionEvent);
override fun onMotionEvent(motionEvent: MotionEvent?) {
nativeOnMotionEvent(motionEvent)
}
@Override // com.bda.controller.ControllerListener
public void onStateEvent(StateEvent stateEvent) {
nativeOnStateEvent(stateEvent);
override fun onStateEvent(stateEvent: 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
class ObbHelper {
ObbHelper() {
}
public static String getObbFileName(Context context, int i) {
return "main." + i + "." + context.getPackageName() + ".obb";
}
internal object ObbHelper {
@JvmStatic
fun getObbFileName(context: Context, versionCode: Int) = "main.$versionCode.${context.packageName}.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;
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;
class PermissionsActivity : AppCompatActivity() {
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
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();
companion object {
private const val SETTINGS_REQUEST_CODE = 100
}
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()) {
initActivity();
initActivity()
} else {
requestPermissions();
requestPermissions()
}
}
private boolean areAllPermissionsGranted() {
for (String permission : getRequiredPermissions()) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
private fun areAllPermissionsGranted(): Boolean {
return getRequiredPermissions().all { permission ->
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
return true;
}
private String[] getRequiredPermissions() {
List<String> permissions = new ArrayList<>();
private fun getRequiredPermissions(): Array<String> {
val permissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
// Android 9 (Pie) и ниже - нужны оба разрешения
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
// Android 10 (Q) - только чтение
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Android 13+ - новые раздельные разрешения для медиа
//permissions.add(Manifest.permission.READ_MEDIA_IMAGES);
//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);
when {
Build.VERSION.SDK_INT <= Build.VERSION_CODES.P -> {
// Android 9 (Pie) и ниже - нужны оба разрешения
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q -> {
// Android 10 (Q) - только чтение
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
// Android 13+ - новые раздельные разрешения для медиа
// permissions.add(Manifest.permission.READ_MEDIA_IMAGES)
// 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 -> {
Log.i("Permissions", e);
});
permissions.forEach { permission ->
Log.i("Permissions", permission)
}
return permissions.toArray(new String[0]);
return permissions.toTypedArray()
}
private void requestPermissions() {
String[] permissions = getRequiredPermissions();
boolean shouldShowRationale = false;
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
shouldShowRationale = true;
break;
}
private fun requestPermissions() {
val permissions = getRequiredPermissions()
val shouldShowRationale = permissions.any { permission ->
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
}
if (shouldShowRationale) {
showPermissionRationaleDialog();
showPermissionRationaleDialog()
} else {
requestPermissionLauncher.launch(permissions);
requestPermissionLauncher.launch(permissions)
}
}
private void showPermissionRationaleDialog() {
new AlertDialog.Builder(this)
.setTitle("Требуются разрешения")
.setMessage("Для корректной работы приложения необходимы разрешения на доступ к хранилищу. Пожалуйста, предоставьте запрашиваемые разрешения.")
.setPositiveButton("OK", (dialog, which) -> requestPermissionLauncher.launch(getRequiredPermissions()))
.setNegativeButton("Отмена", (dialog, which) -> finish())
.setCancelable(false)
.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;
private fun showPermissionRationaleDialog() {
AlertDialog.Builder(this)
.setTitle("Требуются разрешения")
.setMessage("Для корректной работы приложения необходимы разрешения на доступ к хранилищу. Пожалуйста, предоставьте запрашиваемые разрешения.")
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
requestPermissionLauncher.launch(getRequiredPermissions())
}
.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() {
new AlertDialog.Builder(this)
.setTitle("Разрешения отклонены")
.setMessage("Вы навсегда отклонили некоторые разрешения. Чтобы использовать приложение, предоставьте разрешения в настройках.")
.setPositiveButton("Настройки", (dialog, which) -> openAppSettings())
.setNegativeButton("Выход", (dialog, which) -> finish())
.setCancelable(false)
.show();
private fun areSomePermissionsPermanentlyDenied(): Boolean {
return getRequiredPermissions().any { permission ->
!ActivityCompat.shouldShowRequestPermissionRationale(this, permission) &&
ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
}
}
private void openAppSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, SETTINGS_REQUEST_CODE);
private fun showPermanentlyDeniedDialog() {
AlertDialog.Builder(this)
.setTitle("Разрешения отклонены")
.setMessage("Вы навсегда отклонили некоторые разрешения. Чтобы использовать приложение, предоставьте разрешения в настройках.")
.setPositiveButton("Настройки") { dialog, _ ->
dialog.dismiss()
openAppSettings()
}
.setNegativeButton("Выход") { dialog, _ ->
dialog.dismiss()
finish()
}
.setCancelable(false)
.show()
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
private fun openAppSettings() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
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 (areAllPermissionsGranted()) {
initActivity();
initActivity()
} else {
handlePermissionsDenied();
handlePermissionsDenied()
}
}
}
private void initActivity() {
private fun initActivity() {
try {
startActivity(new Intent(this, GameActivityMain.class));
finish();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Ошибка запуска приложения", Toast.LENGTH_SHORT).show();
finish();
startActivity(Intent(this, GameActivityMain::class.java))
finish()
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(this, "Ошибка запуска приложения", Toast.LENGTH_SHORT).show()
finish()
}
}
}
+32 -37
View File
@@ -1,57 +1,52 @@
package com.ea.ironmonkey;
import android.opengl.GLSurfaceView;
package com.ea.ironmonkey
public class RunLoop {
public static final int STATE_RUNNING = 1;
public static final int STATE_STOPPED = 0;
private GLSurfaceView glSurfaceView;
private int state;
import android.opengl.GLSurfaceView
import android.util.Log
private native void nativeOnRunLoopTick();
class RunLoop(private val glSurfaceView: GLSurfaceView) {
public RunLoop(GLSurfaceView gLSurfaceView) {
this.glSurfaceView = gLSurfaceView;
updateRenderMode();
companion object {
private const val TAG = "RunLoop"
private const val STATE_STOPPED = 0
private const val STATE_RUNNING = 1
}
public int getState() {
return this.state;
private var state: Int = STATE_STOPPED
private external fun nativeOnRunLoopTick()
val currentState: Int
get() = state
fun start() {
setState(STATE_RUNNING)
}
public void start() {
setState(1);
fun stop() {
setState(STATE_STOPPED)
}
public void stop() {
setState(0);
fun join() {
setState(STATE_STOPPED)
}
public void join() {
setState(0);
private fun setState(newState: Int) {
state = newState
updateRenderMode()
}
private void setState(int i) {
this.state = i;
updateRenderMode();
}
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;
private fun updateRenderMode() {
Log.v(TAG, "RunLoop.state = $state")
when (state) {
STATE_STOPPED -> glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
STATE_RUNNING -> glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
}
public void onRunLoopTick() {
if (this.state == 1) {
nativeOnRunLoopTick();
fun onRunLoopTick() {
if (state == STATE_RUNNING) {
nativeOnRunLoopTick()
}
}
}
@@ -1,255 +1,258 @@
package com.ea.ironmonkey;
package com.ea.ironmonkey
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;
import android.app.Activity
import android.graphics.BitmapFactory
import android.opengl.GLES20
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;
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;
class SplashScreen(private val activity: Activity) {
public class 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;
companion object {
private const val TAG = "SplashScreen"
}
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()) {
destroy(gl10);
return;
destroy(gl10)
return
}
// Загрузка текстуры
GLES20.glGenTextures(1, _textureId, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _textureId[0]);
GLES20.glGenTextures(1, 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_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_T, GLES20.GL_CLAMP_TO_EDGE);
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_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
// Загрузка изображения
Bitmap bitmap = null;
try {
InputStream is = _activity.getAssets().open("splash.png");
bitmap = BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e(TAG, "Could not load bitmap", e);
val bitmap = try {
activity.assets.open("splash.png").use { stream ->
BitmapFactory.decodeStream(stream)
}
} catch (e: IOException) {
Log.e(TAG, "Could not load bitmap", e)
null
}
if (bitmap != null) {
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
bitmap.recycle()
} else {
Log.e(TAG, "Failed to load bitmap");
destroy(gl10);
return;
Log.e(TAG, "Failed to load bitmap")
destroy(gl10)
return
}
// Проверка ошибок
int error = GLES20.glGetError();
val error = GLES20.glGetError()
if (error != GLES20.GL_NO_ERROR) {
Log.e(TAG, "Texture Load GLError: " + error);
destroy(gl10);
Log.e(TAG, "Texture Load GLError: $error")
destroy(gl10)
}
}
private boolean initRenderer() {
private fun initRenderer(): Boolean {
// Загрузка шейдеров
_vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vShaderStr);
_fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fShaderStr);
vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderStr)
fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderStr)
// Создание программы
_program = GLES20.glCreateProgram();
if (_program == 0) {
Log.e(TAG, "Failed to create program");
return false;
program = GLES20.glCreateProgram()
if (program == 0) {
Log.e(TAG, "Failed to create program")
return false
}
// Прикрепление шейдеров
GLES20.glAttachShader(_program, _vertexShader);
GLES20.glAttachShader(_program, _fragmentShader);
GLES20.glAttachShader(program, vertexShader)
GLES20.glAttachShader(program, fragmentShader)
// Линковка программы
GLES20.glLinkProgram(_program);
GLES20.glLinkProgram(program)
// Проверка статуса линковки
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(_program, GLES20.GL_LINK_STATUS, linkStatus, 0);
val linkStatus = IntArray(1)
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: " + GLES20.glGetProgramInfoLog(_program));
GLES20.glDeleteProgram(_program);
_program = 0;
return false;
Log.e(TAG, "Could not link program: ${GLES20.glGetProgramInfoLog(program)}")
GLES20.glDeleteProgram(program)
program = 0
return false
}
// Получение атрибутов
_attPosition = GLES20.glGetAttribLocation(_program, "a_position");
_attTexCoord = GLES20.glGetAttribLocation(_program, "a_texCoord");
_attSampler = GLES20.glGetUniformLocation(_program, "s_texture");
attPosition = GLES20.glGetAttribLocation(program, "a_position")
attTexCoord = GLES20.glGetAttribLocation(program, "a_texCoord")
attSampler = GLES20.glGetUniformLocation(program, "s_texture")
return true;
return true
}
private int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
private fun loadShader(type: Int, shaderCode: String): Int {
val shader = GLES20.glCreateShader(type)
if (shader == 0) {
Log.e(TAG, "Failed to create shader");
return 0;
Log.e(TAG, "Failed to create shader")
return 0
}
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
// Проверка статуса компиляции
int[] compiled = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
val compiled = IntArray(1)
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader: " + GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
return 0;
Log.e(TAG, "Could not compile shader: ${GLES20.glGetShaderInfoLog(shader)}")
GLES20.glDeleteShader(shader)
return 0
}
return shader;
return shader
}
public boolean draw(GL10 gl10, int width, int height) {
if (_textureId[0] == 0 || _program == 0) {
return false;
fun draw(gl10: GL10?, width: Int, height: Int): Boolean {
if (textureId[0] == 0 || program == 0) {
return false
}
// Расчет координат с уменьшением на 20%
float ratio = (float) width / height;
float imageRatio = 1.0f; // Предполагаем квадратное изображение
val ratio = width.toFloat() / height
val imageRatio = 1.0f // Предполагаем квадратное изображение
float scaleX, scaleY;
if (ratio > imageRatio) {
val (scaleX, scaleY) = if (ratio > imageRatio) {
// Шире, чем изображение
scaleY = 0.8f; // Уменьшаем на 20%
scaleX = imageRatio / ratio * 0.8f;
val scaleY = 0.8f // Уменьшаем на 20%
val scaleX = imageRatio / ratio * 0.8f
scaleX to scaleY
} else {
// Уже, чем изображение
scaleX = 0.8f; // Уменьшаем на 20%
scaleY = ratio / imageRatio * 0.8f;
val scaleX = 0.8f // Уменьшаем на 20%
val scaleY = ratio / imageRatio * 0.8f
scaleX to scaleY
}
// Координаты вершин и текстур
float[] vertices = {
-scaleX, -scaleY, 0.0f, // нижний левый
scaleX, -scaleY, 0.0f, // нижний правый
-scaleX, scaleY, 0.0f, // верхний левый
scaleX, scaleY, 0.0f // верхний правый
};
val vertices = floatArrayOf(
-scaleX, -scaleY, 0.0f, // нижний левый
scaleX, -scaleY, 0.0f, // нижний правый
-scaleX, scaleY, 0.0f, // верхний левый
scaleX, scaleY, 0.0f // верхний правый
)
float[] texCoords = {
0.0f, 1.0f, // нижний левый
1.0f, 1.0f, // нижний правый
0.0f, 0.0f, // верхний левый
1.0f, 0.0f // верхний правый
};
val texCoords = floatArrayOf(
0.0f, 1.0f, // нижний левый
1.0f, 1.0f, // нижний правый
0.0f, 0.0f, // верхний левый
1.0f, 0.0f // верхний правый
)
// Создание буферов
ByteBuffer bb = ByteBuffer.allocateDirect(vertices.length * 4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
val vertexBuffer = ByteBuffer
.allocateDirect(vertices.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.apply {
put(vertices)
position(0)
}
bb = ByteBuffer.allocateDirect(texCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
FloatBuffer texBuffer = bb.asFloatBuffer();
texBuffer.put(texCoords);
texBuffer.position(0);
val texBuffer = ByteBuffer
.allocateDirect(texCoords.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.apply {
put(texCoords)
position(0)
}
// Отрисовка с белым фоном
GLES20.glViewport(0, 0, width, height);
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // Белый цвет
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glViewport(0, 0, width, height)
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) // Белый цвет
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
GLES20.glUseProgram(_program);
GLES20.glUseProgram(program)
// Передача вершин
GLES20.glVertexAttribPointer(_attPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer);
GLES20.glEnableVertexAttribArray(_attPosition);
GLES20.glVertexAttribPointer(attPosition, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer)
GLES20.glEnableVertexAttribArray(attPosition)
// Передача текстурных координат
GLES20.glVertexAttribPointer(_attTexCoord, 2, GLES20.GL_FLOAT, false, 0, texBuffer);
GLES20.glEnableVertexAttribArray(_attTexCoord);
GLES20.glVertexAttribPointer(attTexCoord, 2, GLES20.GL_FLOAT, false, 0, texBuffer)
GLES20.glEnableVertexAttribArray(attTexCoord)
// Активация текстуры
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, _textureId[0]);
GLES20.glUniform1i(_attSampler, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[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(_attTexCoord);
GLES20.glDisableVertexAttribArray(attPosition)
GLES20.glDisableVertexAttribArray(attTexCoord)
return true;
return true
}
public void destroy(GL10 gl10) {
if (_textureId[0] != 0) {
GLES20.glDeleteTextures(1, _textureId, 0);
_textureId[0] = 0;
fun destroy(gl10: GL10?) {
if (textureId[0] != 0) {
GLES20.glDeleteTextures(1, textureId, 0)
textureId[0] = 0
}
if (_program != 0) {
GLES20.glDeleteProgram(_program);
_program = 0;
if (program != 0) {
GLES20.glDeleteProgram(program)
program = 0
}
if (_vertexShader != 0) {
GLES20.glDeleteShader(_vertexShader);
_vertexShader = 0;
if (vertexShader != 0) {
GLES20.glDeleteShader(vertexShader)
vertexShader = 0
}
if (_fragmentShader != 0) {
GLES20.glDeleteShader(_fragmentShader);
_fragmentShader = 0;
}
if (vBuffer != null) {
vBuffer.clear();
vBuffer = null;
if (fragmentShader != 0) {
GLES20.glDeleteShader(fragmentShader)
fragmentShader = 0
}
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
}