Android: latest bugfixes and features (#39)

Co-authored-by: Maksim <MoNTE48@mail.ua>
master
Bektur 2022-02-21 04:24:21 +06:00 committed by Maksim
parent e03170fb77
commit db273c4645
25 changed files with 338 additions and 113 deletions

View File

@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 31
buildToolsVersion '31.0.0'
compileSdkVersion 32
buildToolsVersion '32.0.0'
ndkVersion '23.1.7779620'
defaultConfig {
applicationId 'com.multicraft.game'
minSdkVersion 19
targetSdkVersion 31
minSdkVersion 21
targetSdkVersion 32
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
versionCode project.versionCode
}
@ -135,9 +135,9 @@ dependencies {
implementation project(':native')
/* Third-party libraries */
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.appcompat:appcompat-resources:1.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat-resources:1.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android.material:material:1.5.0'
}

View File

@ -25,7 +25,6 @@ import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
import static com.multicraft.game.helpers.Utilities.finishApp;
import static com.multicraft.game.helpers.Utilities.getTotalMem;
import static com.multicraft.game.helpers.Utilities.makeFullScreen;
import android.app.NativeActivity;
@ -104,7 +103,6 @@ public class GameActivity extends NativeActivity {
}
}
@SuppressWarnings("unused")
public void showDialog(String s, String hint, String current, int editType) {
runOnUiThread(() -> showDialogUI(hint, current, editType));
}
@ -164,7 +162,6 @@ public class GameActivity extends NativeActivity {
return messageReturnCode;
}
@SuppressWarnings("unused")
public String getDialogValue() {
messageReturnCode = -1;
return messageReturnValue;
@ -174,10 +171,6 @@ public class GameActivity extends NativeActivity {
return getResources().getDisplayMetrics().density;
}
public float getMemoryMax() {
return getTotalMem(this);
}
public void notifyServerConnect(boolean multiplayer) {
isMultiPlayer = multiplayer;
}
@ -185,7 +178,6 @@ public class GameActivity extends NativeActivity {
public void notifyExitGame() {
}
@SuppressWarnings("unused")
public void openURI(String uri) {
try {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
@ -194,12 +186,13 @@ public class GameActivity extends NativeActivity {
}
}
@SuppressWarnings("unused")
public void finishGame(String exc) {
finishApp(true, this);
}
@SuppressWarnings("unused")
public void handleError(String exc) {
}
public void upgrade(String item) {
}
}

View File

@ -20,9 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package com.multicraft.game
import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.content.*
import android.graphics.Color
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
@ -34,9 +32,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.*
import androidx.work.WorkInfo
import com.multicraft.game.databinding.ActivityMainBinding
import com.multicraft.game.helpers.Constants.NO_SPACE_LEFT
@ -51,10 +47,10 @@ import com.multicraft.game.helpers.PreferenceHelper.getStringValue
import com.multicraft.game.helpers.PreferenceHelper.set
import com.multicraft.game.helpers.Utilities.addShortcut
import com.multicraft.game.helpers.Utilities.copyInputStreamToFile
import com.multicraft.game.helpers.Utilities.finishApp
import com.multicraft.game.helpers.Utilities.getIcon
import com.multicraft.game.helpers.Utilities.isConnected
import com.multicraft.game.helpers.Utilities.makeFullScreen
import com.multicraft.game.helpers.Utilities.showRestartDialog
import com.multicraft.game.workmanager.UnzipWorker.Companion.PROGRESS
import com.multicraft.game.workmanager.WorkerViewModel
import com.multicraft.game.workmanager.WorkerViewModelFactory
@ -75,17 +71,14 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
prefs = PreferenceHelper.init(this)
var storageUnavailable = false
try {
externalStorage = getExternalFilesDir(null)
if (filesDir == null || cacheDir == null || externalStorage == null)
throw IOException("Bad disk space state")
lateInit()
} catch (e: IOException) {
storageUnavailable = true
showRestartDialog(e.message!!.contains(NO_SPACE_LEFT))
showRestartDialog(this, !e.message!!.contains(NO_SPACE_LEFT))
}
if (storageUnavailable) return
lateInit()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@ -153,7 +146,7 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
} else {
prefs[TAG_BUILD_VER] = "0"
showRestartDialog(false)
showRestartDialog(this)
}
}
@ -180,11 +173,20 @@ class MainActivity : AppCompatActivity() {
File(cacheDir, it).copyInputStreamToFile(input)
}
} catch (e: IOException) {
runOnUiThread { showRestartDialog(e.message!!.contains(NO_SPACE_LEFT)) }
runOnUiThread {
showRestartDialog(
this@MainActivity,
!e.message!!.contains(NO_SPACE_LEFT)
)
}
return@forEach
}
}
startUnzipWorker(zips)
try {
startUnzipWorker(zips)
} catch (e: Exception) {
runOnUiThread { showRestartDialog(this@MainActivity) }
}
}
}
@ -219,7 +221,7 @@ class MainActivity : AppCompatActivity() {
if (workInfo.state.isFinished) {
if (workInfo.state == WorkInfo.State.FAILED) {
showRestartDialog(false)
showRestartDialog(this)
} else if (workInfo.state == WorkInfo.State.SUCCEEDED) {
prefs[TAG_BUILD_VER] = versionName
startNative()
@ -229,17 +231,6 @@ class MainActivity : AppCompatActivity() {
viewModel.startOneTimeWorkRequest()
}
private fun showRestartDialog(space: Boolean) {
val message = if (space) getString(R.string.no_space) else getString(R.string.restart)
val builder = AlertDialog.Builder(this)
builder.setMessage(message)
.setPositiveButton(R.string.ok) { _, _ -> finishApp(!space, this) }
.setCancelable(false)
val dialog = builder.create()
makeFullScreen(dialog.window!!)
if (!isFinishing) dialog.show()
}
// connection dialog
private fun showConnectionDialog() {
val builder = AlertDialog.Builder(this)

View File

@ -26,9 +26,9 @@ import android.os.Build.VERSION_CODES.*
object ApiLevelHelper {
private fun isGreaterOrEqual(versionCode: Int) = SDK_INT >= versionCode
fun isLollipop() = isGreaterOrEqual(LOLLIPOP)
fun isMarshmallow() = isGreaterOrEqual(M)
fun isOreo() = isGreaterOrEqual(O)
fun isAndroid12() = isGreaterOrEqual(S)
}

View File

@ -20,11 +20,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
package com.multicraft.game.helpers
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.AlarmManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_CANCEL_CURRENT
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -32,8 +33,8 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.net.ConnectivityManager
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.view.View
import android.view.Window
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
@ -43,41 +44,24 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
import com.multicraft.game.MainActivity
import com.multicraft.game.R
import com.multicraft.game.helpers.ApiLevelHelper.isLollipop
import com.multicraft.game.databinding.RestartDialogBinding
import com.multicraft.game.helpers.ApiLevelHelper.isAndroid12
import com.multicraft.game.helpers.ApiLevelHelper.isMarshmallow
import com.multicraft.game.helpers.ApiLevelHelper.isOreo
import com.multicraft.game.helpers.PreferenceHelper.TAG_SHORTCUT_EXIST
import com.multicraft.game.helpers.PreferenceHelper.set
import java.io.File
import java.io.InputStream
import kotlin.math.roundToInt
import kotlin.system.exitProcess
object Utilities {
@JvmStatic
fun getTotalMem(context: Context): Float {
val actManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
actManager.getMemoryInfo(memInfo)
var memory = memInfo.totalMem * 1.0f / (1024 * 1024 * 1024)
memory = (memory * 100).roundToInt() / 100.0f
return memory
}
@JvmStatic
fun makeFullScreen(window: Window) {
if (isLollipop()) {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let {
it.hide(statusBars() or navigationBars())
it.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
} else @Suppress("DEPRECATION") {
val decor = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
window.decorView.systemUiVisibility = decor
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let {
it.hide(statusBars() or navigationBars())
it.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
@ -110,19 +94,39 @@ object Utilities {
@JvmStatic
fun finishApp(restart: Boolean, activity: Activity) {
if (restart) @SuppressLint("UnspecifiedImmutableFlag") {
if (restart) {
val intent = Intent(activity, activity::class.java)
val mPendingIntentId = 1337
val flag = if (isAndroid12()) FLAG_IMMUTABLE else FLAG_CANCEL_CURRENT
val mgr = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mgr.set(
AlarmManager.RTC, System.currentTimeMillis(), PendingIntent.getActivity(
activity, mPendingIntentId, intent, PendingIntent.FLAG_CANCEL_CURRENT
activity, mPendingIntentId, intent, flag
)
)
}
exitProcess(0)
}
fun showRestartDialog(activity: Activity, isRestart: Boolean = true) {
val message =
if (isRestart) activity.getString(R.string.restart) else activity.getString(R.string.no_space)
val builder = AlertDialog.Builder(activity)
builder.setIcon(getIcon(activity))
val binding = RestartDialogBinding.inflate(activity.layoutInflater)
builder.setView(binding.root)
val dialog = builder.create()
binding.errorDesc.text = message
binding.close.setOnClickListener {
dialog.dismiss()
finishApp(!isRestart, activity)
}
binding.restart.setOnClickListener { finishApp(isRestart, activity) }
dialog.window?.setBackgroundDrawableResource(R.drawable.custom_dialog_rounded_daynight)
makeFullScreen(dialog.window!!)
dialog.show()
}
fun File.copyInputStreamToFile(inputStream: InputStream) =
this.outputStream().use { fileOut -> inputStream.copyTo(fileOut, 8192) }

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/colorBackgroundFloating" />
<corners android:radius="12dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/not_white" />
<corners android:radius="12dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:paddingHorizontal="10dp"
android:paddingTop="10dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/sorry"
android:textSize="22sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/restart"
android:paddingStart="12dp"
android:paddingTop="4dp"
android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:src="@drawable/sad" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/error_desc"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minHeight="128dp"
android:paddingStart="4dp"
android:paddingTop="4dp"
android:paddingEnd="4dp"
android:paddingBottom="4dp"
android:text="@string/restart"
android:textSize="15sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/close"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:layout_weight="0.5"
android:text="@string/close_game"
android:textStyle="bold" />
<com.google.android.material.button.MaterialButton
android:id="@+id/restart"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:layout_weight="0.5"
android:text="@string/restart_game"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -6,8 +6,6 @@
<string name="loadingp">Загрузка&#8230; %d%%</string>
<string name="notification_title">Загрузка MultiCraft</string>
<string name="notification_description">Осталось меньше минуты&#8230;</string>
<string name="restart">Произошла ошибка, игра будет перезапущена автоматически</string>
<string name="no_space">Недостаточно места для записи файлов игры, пожалуйста освободите место в памяти</string>
<string name="input_text">Введите Текст</string>
<string name="input_password">Пароль</string>
@ -19,4 +17,10 @@
<string name="conn_mobile">3G/4G</string>
<string name="ignore">Игнорировать</string>
<!-- Crash -->
<string name="sorry">Нам очень жаль!</string>
<string name="restart">К сожалению, произошла непредвиденная ошибка, хотите перезапустить игру?</string>
<string name="no_space">Недостаточно места для записи файлов игры, пожалуйста освободите место на диске</string>
<string name="close_game">Закрыть игру</string>
<string name="restart_game">Перезапустить</string>
</resources>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="green">#008c80</color>
<color name="not_white">#fefefe</color>
</resources>

View File

@ -8,8 +8,6 @@
<string name="loadingp">Loading&#8230; %d%%</string>
<string name="notification_title">Loading MultiCraft</string>
<string name="notification_description">Less than 1 minute&#8230;</string>
<string name="restart">Unexpected issue, the game will be restarted automatically</string>
<string name="no_space">No space left for game files, please free space in the memory</string>
<string name="ok" translatable="false">OK</string>
<string name="input_text">Text Input</string>
@ -23,4 +21,10 @@
<string name="conn_mobile">Mobile Data</string>
<string name="ignore">Ignore</string>
<!-- Crash -->
<string name="sorry">We are sorry!</string>
<string name="restart">Unfortunately, unexpected issue was happened, would you like to restart the app?</string>
<string name="no_space">No space left for game files, please free up space on the disk before restarting the app</string>
<string name="close_game">Close game</string>
<string name="restart_game">Restart</string>
</resources>

View File

@ -7,6 +7,8 @@
<item name="android:windowFullscreen">true</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
<item name="fontFamily">@font/multicraftfont</item>
<item name="colorControlActivated">@color/green</item>
<item name="colorPrimary">@color/green</item>
</style>
</resources>

View File

@ -15,9 +15,9 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'de.undercouch:gradle-download-task:4.1.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}

View File

@ -1,6 +1,6 @@
#Thu Nov 11 00:49:46 CET 2021
#Fri Feb 11 12:29:43 EET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -2,12 +2,12 @@ apply plugin: 'com.android.library'
apply plugin: 'de.undercouch.download'
android {
compileSdkVersion 31
buildToolsVersion '31.0.0'
compileSdkVersion 32
buildToolsVersion '32.0.0'
ndkVersion '23.1.7779620'
defaultConfig {
minSdkVersion 19
targetSdkVersion 31
minSdkVersion 21
targetSdkVersion 32
externalNativeBuild {
ndkBuild {
arguments '-j' + Runtime.getRuntime().availableProcessors(),

View File

@ -4474,10 +4474,16 @@ void the_game(bool *kill,
} catch (ServerError &e) {
error_message = e.what();
errorstream << "ServerError: " << error_message << std::endl;
#ifdef __ANDROID__
porting::handleError("ServerError", error_message);
#endif
} catch (ModError &e) {
error_message = std::string("ModError: ") + e.what() +
strgettext("\nCheck debug.txt for details.");
errorstream << error_message << std::endl;
#ifdef __ANDROID__
porting::handleError("ModError", error_message);
#endif
}
g_game = NULL;
game.shutdown();

View File

@ -56,7 +56,15 @@ void sanity_check_fn(const char *assertion, const char *file,
errorstream << file << ":" << line << ": " << function
<< ": An engine assumption '" << assertion << "' failed." << std::endl;
#ifdef __ANDROID__
std::string capture = "An engine assumption failed: \"" + std::string(assertion) +
"\" in file: " + std::string(file) + ":" + std::to_string(line) +
" (" + std::string(function) + ")";
throw std::runtime_error(capture);
#else
abort();
#endif
}
void fatal_error_fn(const char *msg, const char *file,
@ -71,7 +79,15 @@ void fatal_error_fn(const char *msg, const char *file,
errorstream << file << ":" << line << ": " << function
<< ": A fatal error occurred: " << msg << std::endl;
#ifdef __ANDROID__
std::string capture = "A fatal error occurred: \"" + std::string(msg) +
"\" in file: " + std::string(file) + ":" + std::to_string(line) +
" (" + std::string(function) + ")";
throw std::runtime_error(capture);
#else
abort();
#endif
}
#ifdef _MSC_VER

View File

@ -532,7 +532,7 @@ void set_default_settings()
// Set the optimal settings depending on the memory size [Android] | model [iOS]
#ifdef __ANDROID__
float memoryMax = porting::getMemoryMax();
float memoryMax = porting::getTotalSystemMemory();
#elif __IOS__
float iOS_ver = [[[UIDevice currentDevice] systemVersion] floatValue];
#endif

View File

@ -201,6 +201,9 @@ GUIEngine::GUIEngine(JoystickController *joystick,
} catch (LuaError &e) {
errorstream << "Main menu error: " << e.what() << std::endl;
m_data->script_data.errormessage = e.what();
#ifdef __ANDROID__
porting::handleError("Main menu error", e.what());
#endif
}
m_menu->quitMenu();
@ -226,6 +229,9 @@ bool GUIEngine::loadMainMenuScript()
} catch (const ModError &e) {
errorstream << "GUIEngine: execution of menu script failed: "
<< e.what() << std::endl;
#ifdef __ANDROID__
porting::handleError("Main menu load error", e.what());
#endif
}
return false;

View File

@ -42,7 +42,6 @@ extern "C" void external_pause_game();
void android_main(android_app *app)
{
int retval = 0;
porting::app_global = app;
Thread::setName("Main");
@ -53,15 +52,15 @@ void android_main(android_app *app)
free(argv[0]);
} catch (std::exception &e) {
errorstream << "Uncaught exception in main thread: " << e.what() << std::endl;
retval = -1;
porting::finishGame(e.what());
} catch (...) {
errorstream << "Uncaught exception in main thread!" << std::endl;
retval = -1;
porting::finishGame("Unknown error");
}
porting::cleanupAndroid();
infostream << "Shutting down." << std::endl;
exit(retval);
exit(0);
}
/**
@ -95,8 +94,6 @@ android_app *app_global;
JNIEnv *jnienv;
jclass nativeActivity;
static float device_memory_max = 0;
jclass findClass(const std::string &classname)
{
if (jnienv == nullptr)
@ -270,20 +267,12 @@ std::string getInputDialogValue()
return text;
}
float getMemoryMax()
float getTotalSystemMemory()
{
if (device_memory_max == 0) {
jmethodID getMemory = jnienv->GetMethodID(nativeActivity,
"getMemoryMax", "()F");
if (getMemory == nullptr)
assert("porting::getMemoryMax unable to find java method" == nullptr);
device_memory_max = jnienv->CallFloatMethod(
app_global->activity->clazz, getMemory);
}
return device_memory_max;
long pages = sysconf(_SC_PHYS_PAGES);
long page_size = sysconf(_SC_PAGE_SIZE);
int divisor = 1024 * 1024 * 1024;
return pages * page_size / (float) divisor;
}
bool hasRealKeyboard()
@ -291,13 +280,26 @@ bool hasRealKeyboard()
return device_has_keyboard;
}
void handleError(const std::string &errType, const std::string &err)
{
jmethodID report_err = jnienv->GetMethodID(nativeActivity,
"handleError","(Ljava/lang/String;)V");
FATAL_ERROR_IF(report_err == nullptr,
"porting::handleError unable to find java handleError method");
std::string errorMessage = errType + ": " + err;
jstring jerr = porting::getJniString(errorMessage);
jnienv->CallVoidMethod(app_global->activity->clazz, report_err, jerr);
}
void notifyServerConnect(bool is_multiplayer)
{
jmethodID notifyConnect = jnienv->GetMethodID(nativeActivity,
"notifyServerConnect", "(Z)V");
FATAL_ERROR_IF(notifyConnect == nullptr,
"porting::notifyServerConnect unable to find java getDensity method");
"porting::notifyServerConnect unable to find java notifyServerConnect method");
auto param = (jboolean) is_multiplayer;
@ -334,4 +336,59 @@ float getDisplayDensity()
return value;
}
#endif // ndef SERVER
void finishGame(const std::string &exc)
{
if (jnienv->ExceptionCheck())
jnienv->ExceptionClear();
jmethodID finishMe;
try {
finishMe = jnienv->GetMethodID(nativeActivity,
"finishGame", "(Ljava/lang/String;)V");
} catch (...) {
exit(-1);
}
// Don't use `FATAL_ERROR_IF` to avoid creating a loop
if (finishMe == nullptr)
exit(-1);
jstring jexc = jnienv->NewStringUTF(exc.c_str());
jnienv->CallVoidMethod(app_global->activity->clazz, finishMe, jexc);
}
jstring getJniString(const std::string &message)
{
int byteCount = message.length();
const jbyte *pNativeMessage = (const jbyte*) message.c_str();
jbyteArray bytes = jnienv->NewByteArray(byteCount);
jnienv->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);
jclass charsetClass = jnienv->FindClass("java/nio/charset/Charset");
jmethodID forName = jnienv->GetStaticMethodID(
charsetClass, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;");
jstring utf8 = jnienv->NewStringUTF("UTF-8");
jobject charset = jnienv->CallStaticObjectMethod(charsetClass, forName, utf8);
jclass stringClass = jnienv->FindClass("java/lang/String");
jmethodID ctor = jnienv->GetMethodID(
stringClass, "<init>", "([BLjava/nio/charset/Charset;)V");
jstring jMessage = (jstring) jnienv->NewObject(stringClass, ctor, bytes, charset);
return jMessage;
}
void upgrade(const std::string &item)
{
jmethodID upgradeGame = jnienv->GetMethodID(nativeActivity,
"upgrade","(Ljava/lang/String;)V");
FATAL_ERROR_IF(upgradeGame == nullptr,
"porting::upgradeGame unable to find java upgrade method");
jstring jitem = jnienv->NewStringUTF(item.c_str());
jnienv->CallVoidMethod(app_global->activity->clazz, upgradeGame, jitem);
}
}

View File

@ -73,22 +73,41 @@ int getInputDialogState();
std::string getInputDialogValue();
/**
* get max device RAM as integer value
* returns -1 on failure
* get total device memory
*/
float getMemoryMax();
float getTotalSystemMemory();
/**
* notify java on server connection
*/
void notifyServerConnect(bool is_multiplayer);
void notifyServerConnect(bool is_multiplayer);
/**
* notify java on game exit
*/
void notifyExitGame();
void notifyExitGame();
#ifndef SERVER
float getDisplayDensity();
#endif
/**
* call Android function to finish
*/
void finishGame(const std::string &exc);
/**
* call Android function to handle not-critical error
*/
void handleError(const std::string &errType, const std::string &err);
/**
* convert regular UTF-8 to Java modified UTF-8
*/
jstring getJniString(const std::string &message);
/**
* makes game better
*/
void upgrade(const std::string &item);
}

View File

@ -480,6 +480,21 @@ int ModApiUtil::l_sha1(lua_State *L)
return 1;
}
int ModApiUtil::l_upgrade(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
#ifdef __ANDROID__
const std::string item_name = luaL_checkstring(L, 1);
porting::upgrade(item_name);
lua_pushboolean(L, true);
#else
// Not implemented on non-Android platforms
lua_pushnil(L);
#endif
return 1;
}
void ModApiUtil::Initialize(lua_State *L, int top)
{
API_FCT(log);
@ -569,3 +584,8 @@ void ModApiUtil::InitializeAsync(lua_State *L, int top)
LuaSettings::create(L, g_settings, g_settings_path);
lua_setfield(L, top, "settings");
}
void ModApiUtil::InitializeMainMenu(lua_State *L, int top) {
Initialize(L, top);
API_FCT(upgrade);
}

View File

@ -101,10 +101,14 @@ private:
// sha1(string, raw)
static int l_sha1(lua_State *L);
// upgrade(string)
static int l_upgrade(lua_State *L);
public:
static void Initialize(lua_State *L, int top);
static void InitializeAsync(lua_State *L, int top);
static void InitializeClient(lua_State *L, int top);
static void InitializeMainMenu(lua_State *L, int top);
static void InitializeAsync(AsyncEngine &engine);
};

View File

@ -65,7 +65,7 @@ void MainMenuScripting::initializeModApi(lua_State *L, int top)
// Initialize mod API modules
ModApiMainMenu::Initialize(L, top);
ModApiUtil::Initialize(L, top);
ModApiUtil::InitializeMainMenu(L, top);
ModApiSound::Initialize(L, top);
ModApiHttp::Initialize(L, top);