diff --git a/.classpath b/.classpath index f65b511b60..bdc87b9706 100644 --- a/.classpath +++ b/.classpath @@ -16,7 +16,6 @@ - @@ -24,7 +23,7 @@ - - - + + + diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml new file mode 100644 index 0000000000..710be77781 --- /dev/null +++ b/.github/workflows/android-build.yml @@ -0,0 +1,62 @@ +name: Android Build Test + +on: + push: + paths: + - 'app/android/**' + pull_request: + paths: + - 'app/android/**' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - id: job_info + uses: daleyjem/gh-job-id@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + job_name: 'build' + + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + - name: Validate Gradle Wrapper .jar + run: | + cd app/android/gradle/wrapper + curl --location --output gradle-wrapper.jar.sha256 \ + https://services.gradle.org/distributions/gradle-7.5.1-wrapper.jar.sha256 + echo " gradle-wrapper.jar" >> gradle-wrapper.jar.sha256 + sha256sum --check gradle-wrapper.jar.sha256 + + - name: Build APKs with Ant + uses: cedx/setup-ant@v6 + with: + version: '1.10' + - run: ant -noinput -buildfile build.xml android -Dgradle.params="assembleDebug assembleRelease" + + - name: Failure Report + uses: sarisia/actions-status-discord@v1 + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVELOPMENT }} + with: + title: 'Android Build Failed' + description: '${{ steps.job_info.outputs.html_url }}' + nodetail: true + - uses: sarisia/actions-status-discord@v1 + if: failure() + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEVLOG }} + with: + title: 'Android Build Failed' + description: '${{ steps.job_info.outputs.html_url }}' + nodetail: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1aaaeb4e51..3c5094235c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,15 +29,15 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 # Override language selection by uncommenting this and choosing your languages # with: # languages: go, javascript, csharp, python, cpp, java # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -46,9 +46,7 @@ jobs: # and modify them (or add more) to build your code if your project # uses a compiled language - #- run: | - # make bootstrap - # make release + - run: ant -noinput -f build.xml clean compile - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/prerelease_webview_client.yml b/.github/workflows/prerelease_webview_client.yml index 3a43777f48..b66f74adfa 100644 --- a/.github/workflows/prerelease_webview_client.yml +++ b/.github/workflows/prerelease_webview_client.yml @@ -39,7 +39,7 @@ jobs: - name: Build APKs run: > ant -noinput -buildfile build.xml dist_android_client - -Dgradle.params="--no-daemon -Pandroid.ver=${{ steps.ver.outputs.value }} assembleDebug assemblePrerelease" + -Dgradle.params="-Pandroid.ver=${{ steps.ver.outputs.value }} assembleDebug assembleRelease" - name: Publish uses: softprops/action-gh-release@v1 diff --git a/.gitignore b/.gitignore index 7b1c018c4b..adf7a8202c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,7 +40,6 @@ javadocs statserver_stats.xml /*.ks build_client_data -/bin .byecycle build_server_xmlconf *.umlproject @@ -49,3 +48,5 @@ build_server_xmlconf /map.map /hs_err_*.log /node_modules/ +/log/ +/build-archive/ diff --git a/.project b/.project index 8aa95068ea..a3d0298ad9 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.wst.common.project.facet.core.nature + + + 1771748271774 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c6556475f0..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: java -before_install: - - pip install --user codecov -dist: bionic -addons: - apt: - packages: - - ant-optional -service: - - xvfb -script: xvfb-run -e /dev/stdout -a ant test -notifications: - irc: - channels: - - "irc.libera.chat#arianne" - - "irc.libera.chat#arianne-devel" - on_success: change - on_failure: change - use_notice: false - skip_join: false -after_success: - - codecov diff --git a/README.md b/README.md index bc2efc588a..8b25a90318 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Stendhal is a fully fledged multiplayer online adventures game (MORPG). It is co Stendhal features a new, rich and expanding world in which you can explore towns, buildings, plains, caves and dungeons. You will meet NPCs and acquire tasks and quests for valuable experience. -Your character will develop and grow and with each new level up become stronger and better. With the money you acquire you can buy new items and improve your armour and weapons. And for the blood thirsty of you; satisfy your killing desires by roaming the world in search of evil monsters! +Your character will develop and grow and with each new level up become stronger and better. With the money you acquire you can buy new items and improve your armor and weapons. And for the blood thirsty of you; satisfy your killing desires by roaming the world in search of evil monsters! Stendhal is platform independent, and completely open source. @@ -20,19 +20,38 @@ So what are you waiting for?! A whole new world awaits... ## How to play -[![Play now](https://arianne-project.org/images/playbutton.png)](https://stendhalgame.org/account/mycharacters.html)         +### Web Browser + +[![Play now](https://arianne-project.org/images/playbutton.png)](https://stendhalgame.org/account/mycharacters.html) + +Play in any web browser for desktop or mobile with JavaScript support. Click on the "play now" +button to create or log in to your account. + +### Mobile + +Play using the [progressive web app](https://en.wikipedia.org/wiki/Progressive_web_app). Just visit +[stendhalgame.org](https://stendhalgame.org/) in your mobile browser and click "install" when prompted. + +[](https://f-droid.org/packages/org.stendhalgame.client/) + +Play on your Android device using the dedicated app. You can get it from the +[F-Droid App Store](https://f-droid.org/) or download it from [stendhalgame.org](https://stendhalgame.org/download.html). + +### Classic Desktop + [![Download](https://stendhalgame.org/images/downloadbutton.png)](https://arianne-project.org/download/stendhal.zip) -You need Java which you can download from [https://www.java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in most Linux distributions. Both Oracle Java and OpenJDK are supported. +Play using the classic desktop client. You will need Java which you can download from +[java.com](https://www.java.com/en/download) on Microsoft Windows. Java is included in +most Linux distributions. Both Oracle Java and OpenJDK are supported. -Please download Stendhal from [https://stendhalgame.org](https://stendhalgame.org)
-You can double click on stendhal-starter.jar and the client will run.
-If you prefer to run from command line, then just execute this command -in the stendhal folder: +Please download Stendhal from [stendhalgame.org](https://stendhalgame.org/download.html).
+You can double click on `stendhal-starter.jar` and the client will run.
+If you prefer to run from command line, then just execute this command in the stendhal folder: `java -jar stendhal-starter.jar` -There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen. +There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there is Help from the client menu icons in the upper right hand side of the game screen. ## Development environment @@ -44,13 +63,18 @@ There is a [manual](https://stendhalgame.org/wiki/Stendhal_Manual), and there i ## Legal -The server and java client are free software; you can redistribute it and/or modify it under the terms of the **GNU General Public License** 2 or later as published by the Free Software Foundation. -The web client is free software; you can redistribute it and/or modify it under the terms of the **GNU Affero General Public License** 3 or later as published by the Free Software Foundation. +Stendhal is free software. You can redistribute it and/or modify under the following terms: -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [LICENSE.txt](LICENSE.txt) file for more details. +- The **server**, **java client**, and **Android client**: *GNU General Public License* 2 or later as published by the Free Software Foundation +- The **web client** and **native client**: *GNU Affero General Public License* 3 or later as published by the Free Software Foundation with the definition of "System libraries" being extended to include authentication providers and software distribution platforms. +- The **nativehelper** extensions: *GNU Lesser General Public License* 3 or later as published by the Free Software Foundation + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +[LICENSE.txt](LICENSE.txt) and [src/js/LICENSE.html](src/js/LICENSE.html) file for more details. Stendhal(c) is copyright of Miguel Angel Blanch Lardin, 2005-2008, arianne_rpg at users dot sourceforge dot net and others
-Stendhal(c) is copyright of the Arianne Project, 2006-2023, arianne-general at lists dot sourceforge dot net +Stendhal(c) is copyright of the Arianne Project, 2006-2026, arianne-general at lists dot sourceforge dot net -Please have a look at the list of [contributors](https://github.com/arianne/stendhal/blob/master/doc/contributors.md#contributors). +Please have a look at the list of [contributors](doc/contributors.md#contributors). diff --git a/android/TODO.txt b/android/TODO.txt deleted file mode 100644 index 50d5bf8d8f..0000000000 --- a/android/TODO.txt +++ /dev/null @@ -1,13 +0,0 @@ - -TODO: -- show input preview when typing -- scale page to fit inside view -- create activity for about information -- add credits to about information -- switch to Log4j for logging (maybe) -- use center of d-pad for setting position -- d-pads need to be different sizes for different resolutions - - either use unique PNGs or convert to scalable SVGs - -BUILD TODO: -- script to download Android SDK diff --git a/android/app/build.gradle b/android/app/build.gradle deleted file mode 100644 index 6d0d72c02d..0000000000 --- a/android/app/build.gradle +++ /dev/null @@ -1,79 +0,0 @@ -plugins { - id 'com.android.application' -} - -android { - compileSdk 32 - - def buildDate = new Date().format("yyyyMMdd") - - def appVer - if (project.hasProperty("android.ver") && project["android.ver"]) { - appVer = project["android.ver"] - } else { - appVer = "${buildDate}" - } - - if (project.hasProperty("android.rev") && project["android.rev"]) { - appVer += "-" + project["android.rev"] - } - - def outputName = "stendhal-webview-${appVer}" - - defaultConfig { - applicationId "org.arianne.stendhal.client" - minSdk 21 - targetSdk 32 - versionCode 1 - versionName "${appVer}" - setProperty("archivesBaseName", "${outputName}") - setProperty("buildDir", "${projectDir}/../../build/build_android_client") - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - targetSdkVersion 32 - minSdkVersion 21 - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - - prerelease { - versionNameSuffix "-pre" - signingConfig signingConfigs.debug - } - - debug { - applicationIdSuffix ".debug" - versionNameSuffix "-debug" - } - } - - buildTypes.all { type -> - if (!type.name.equals("debug")) { - sourceSets[type.name].java { srcDirs "src/shared/java/" } - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - compileSdkVersion 32 - buildToolsVersion '32.0.0' - - lintOptions { - abortOnError false - } -} - -dependencies { - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.4.0' - //testImplementation 'junit:junit:4.+' - //androidTestImplementation 'androidx.test.ext:junit:1.1.2' - //androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - implementation files('../../libs/marauroa-client-only.jar') -} diff --git a/android/app/src/debug/java/org/arianne/stendhal/client/DebugLog.java b/android/app/src/debug/java/org/arianne/stendhal/client/DebugLog.java deleted file mode 100644 index a8c9d90109..0000000000 --- a/android/app/src/debug/java/org/arianne/stendhal/client/DebugLog.java +++ /dev/null @@ -1,157 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.util.Log; -import androidx.appcompat.app.AppCompatActivity; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - - -/** - * A class to output debugging information to logcat file. - */ -public class DebugLog { - - private static File logsDir; - - private static AppCompatActivity mainActivity; - - public static enum DebugLevel { - INFO("INFO"), - WARN("WARN"), - ERROR("ERROR"), - DEBUG("DEBUG"); - - private final String label; - - private DebugLevel(final String label) { - this.label = label; - } - - public String getLabel() { - return label; - } - } - - private static DebugLog instance; - - - public static DebugLog get() { - if (instance == null) { - instance = new DebugLog(); - } - - return instance; - } - - public static void init(final File dir, final AppCompatActivity activity) { - logsDir = new File(dir.getPath() + "/logs"); - mainActivity = activity; - - writeLine("\n // -- debugging initialized -- //", null); - writeLine("logs directory: " + logsDir.getPath()); - } - - public static void writeLine(final String text) { - writeLine(text, DebugLevel.DEBUG); - } - - public static void writeLine(String text, final DebugLevel level) { - if (logsDir == null) { - System.err.println("ERROR: DebugLog not initialized. Call DebugLog.init."); - return; - } - - if (!logsDir.exists()) { - logsDir.mkdirs(); - } - - if (level != null) { - text = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new Date()) - + ": " + level.label + ": " + text; - } - text = text + "\n"; - - final String logFile = logsDir.getPath() + "/debug-" - + new SimpleDateFormat("yyyy.MM.dd").format(new Date()) + ".txt"; - - BufferedWriter buffer; - try { - buffer = new BufferedWriter(new FileWriter(logFile, true)); - buffer.write(text); - if (buffer != null) { - buffer.close(); - } - } catch (final IOException e) { - System.err.println("ERROR: " + e.getMessage()); - } - } - - public static void info(final String text) { - Log.i("DebugLog", text); - writeLine(text, DebugLevel.INFO); - } - - public static void warn(final String text) { - Log.w("DebugLog", text); - writeLine(text, DebugLevel.WARN); - } - - public static void error(final String text) { - Log.e("DebugLog", text); - writeLine(text, DebugLevel.ERROR); - } - - public static void debug(final String text) { - Log.d("DebugLog", text); - writeLine(text, DebugLevel.DEBUG); - } - - public static void notify(final String message) { - notify(message, DebugLevel.DEBUG); - } - - public static void notify(final String message, DebugLevel level) { - if (mainActivity == null) { - System.err.println("ERROR: DebugLog not initialized. Call DebugLog.init."); - return; - } - - if (level == null) { - level = DebugLevel.DEBUG; - } - - final AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity); - builder.setTitle(level.label); - builder.setMessage(message); - - builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { - public void onClick(final DialogInterface dialog, final int id) { - dialog.cancel(); - } - }); - - builder.create().show(); - } - - public static String getLogsDir() { - return logsDir.getPath(); - } -} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 6e35947c74..0000000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/java/org/arianne/stendhal/client/ClientView.java b/android/app/src/main/java/org/arianne/stendhal/client/ClientView.java deleted file mode 100644 index bc60dd81c7..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/ClientView.java +++ /dev/null @@ -1,503 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client; - -import android.app.AlertDialog; -import android.content.pm.ApplicationInfo; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.Build; -import android.util.AttributeSet; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputConnection; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.webkit.DownloadListener; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ImageView; - -import marauroa.common.Pair; - -import org.arianne.stendhal.client.input.DPad; -import org.arianne.stendhal.client.js.JSInterface; -import org.arianne.stendhal.client.sound.MusicPlayer; - - -public class ClientView extends WebView { - - private static ClientView instance; - - private ImageView splash; - - private final String defaultServer = "https://stendhalgame.org/"; - private String clientUrlSuffix = "client"; - - private boolean testing = false; - private Boolean debugging; - private static PageId currentPage; - // denotes previous touch was remapped to mouse event - private boolean touchOverridden = false; - - - /** - * Retrieves the static instance. - */ - public static ClientView get() { - return instance; - } - - public ClientView(final Context ctx) { - super(ctx); - onInit(); - } - - public ClientView(final Context ctx, final AttributeSet attrs) { - super(ctx, attrs); - onInit(); - } - - public ClientView(final Context ctx, final AttributeSet attrs, final int style) { - super(ctx, attrs, style); - onInit(); - } - - public ClientView(final Context ctx, final AttributeSet attrs, final int style, - final boolean prvtBrowse) { - super(ctx, attrs, style, prvtBrowse); - onInit(); - } - - private void onInit() { - instance = this; - - setBackgroundColor(android.graphics.Color.TRANSPARENT); - - final WebSettings viewSettings = getSettings(); - viewSettings.setJavaScriptEnabled(true); - viewSettings.setDomStorageEnabled(true); - - // keep elements in position in portrait mode - viewSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);// <-- SINGLE_COLUMN deprecated - viewSettings.setLoadWithOverviewMode(true); - viewSettings.setUseWideViewPort(true); - - // disable zoom - viewSettings.setSupportZoom(false); - viewSettings.setBuiltInZoomControls(false); - viewSettings.setDisplayZoomControls(false); - - if (debugEnabled()) { - // make WebView debuggable for debug builds - setWebContentsDebuggingEnabled(true); - } - - initWebViewClient(); - initTouchHandler(); - initJSInterface(); - initDownloadHandler(); - } - - private void initWebViewClient() { - // XXX: not sure setting WebChromClient is doing anything, was recommended to - // fix touchmove events not registering - setWebChromeClient(new WebChromeClient()); - - setWebViewClient(new WebViewClient() { - /* handle changing URLs */ - @Override - public boolean shouldOverrideUrlLoading(final WebView view, final String url) { - if (!isInternalUrl(url)) { - // FIXME: should we ask for confirmation? - MainActivity.get().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); - return true; - } - - view.loadUrl(checkClientUrl(url)); - return false; - } - - @Override - public void onPageStarted(final WebView view, final String url, final Bitmap favicon) { - DebugLog.debug("loading URL: " + url); - - MusicPlayer.stopMusic(); - super.onPageStarted(view, url, favicon); - } - - @Override - public void onPageFinished(final WebView view, final String url) { - super.onPageFinished(view, url); - - if (isClientUrl(url)) { - currentPage = PageId.WEBCLIENT; - } else if (url.equals("") || url.equals("about:blank")) { - currentPage = PageId.TITLE; - if (PreferencesActivity.getBoolean("title_music", true)) { - playTitleMusic(); - } - } else { - currentPage = PageId.OTHER; - } - Menu.get().updateButtons(); - - // only show d-pad on client screen - final DPad pad = DPad.getCurrentPad(); - if (pad != null && PreferencesActivity.getBoolean("show_dpad", false)) { - if (isGameActive()) { - pad.show(); - } else { - pad.hide(); - } - } - - DebugLog.debug("page id: " + currentPage); - } - }); - } - - private void initTouchHandler() { - setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent event) { - if (touchOverridden) { - // reset touch event state - touchOverridden = false; - return false; - } - - final MotionEvent.PointerProperties[] props = {new MotionEvent.PointerProperties()}; - event.getPointerProperties(0, props[0]); - - if (isGameActive() && PreferencesActivity.getBoolean("map_touches", false) - && (props[0].toolType == MotionEvent.TOOL_TYPE_FINGER - || props[0].toolType == MotionEvent.TOOL_TYPE_STYLUS)) { - Integer action = event.getAction(); - - /* - switch (action) { - case MotionEvent.ACTION_DOWN: - action = MotionEvent.ACTION_BUTTON_PRESS; - break; - case MotionEvent.ACTION_UP: - action = MotionEvent.ACTION_BUTTON_RELEASE; - break; - } - */ - - DebugLog.debug("mapping touch to mouse event"); - - props[0].toolType = MotionEvent.TOOL_TYPE_MOUSE; - - final MotionEvent.PointerCoords[] coords = {new MotionEvent.PointerCoords()}; - event.getPointerCoords(0, coords[0]); - - touchOverridden = true; - - view.dispatchTouchEvent(MotionEvent.obtain(event.getDownTime(), - event.getEventTime(), event.getAction(), event.getPointerCount(), props, - coords, event.getMetaState(), MotionEvent.BUTTON_PRIMARY, - event.getXPrecision(), event.getYPrecision(), event.getDeviceId(), - event.getEdgeFlags(), event.getSource(), event.getFlags())); - - return true; // consume old event - } - - return false; - } - }); - } - - @Override - public InputConnection onCreateInputConnection(final EditorInfo outAttrs) { - // allows capturing soft keyboard events - return new BaseInputConnection(this, false); - } - - @Override - public boolean dispatchKeyEvent(final KeyEvent event) { - final boolean ret = super.dispatchKeyEvent(event); - - // hide keyboard when "enter" pressed - if (isGameActive() && event.getAction() == KeyEvent.ACTION_UP - && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { - ((InputMethodManager) MainActivity.get() - .getSystemService(Context.INPUT_METHOD_SERVICE)) - .hideSoftInputFromWindow(this.getWindowToken(), 0); - } - - return ret; - } - - private void initJSInterface() { - addJavascriptInterface(JSInterface.get(), "jsi"); - } - - /** - * Handles downloading screenshot created by web client. - */ - private void initDownloadHandler() { - setDownloadListener(new DownloadListener() { - @Override - public void onDownloadStart(final String url, final String userAgent, - final String contentDisposition, final String mimetype, - final long contentLength) { - - final Pair res = new DownloadHandler().download(url, mimetype); - if (res.first()) { - final String msg = "Downloaded file: " + res.second(); - DebugLog.debug(msg); - Notifier.toast(msg); - } else { - final String msg = res.second(); - DebugLog.error(msg); - Notifier.toast("ERROR: " + msg); - } - } - }); - } - - /** - * Shows initial title screen. - */ - public void loadTitleScreen() { - setSplashResource(R.drawable.splash); - loadUrl("about:blank"); - Menu.get().show(); - } - - /** - * Attempts to connect to client host. - */ - public void loadLogin() { - if (debugEnabled() && PreferencesActivity.getString("client_url").trim().equals("")) { - // debug builds support choosing between main & test server - selectServer(); - } else { - onSelectServer(); - } - } - - /** - * Sets the background image. - * - * @param resId - * Resource ID. - */ - private void setSplashResource(final int resId) { - if (splash == null) { - splash = (ImageView) MainActivity.get().findViewById(R.id.splash); - splash.setBackgroundColor(android.graphics.Color.TRANSPARENT); - } - - splash.setImageResource(resId); - } - - /** - * Opens a message dialog for user to choose between main & test servers. - */ - private void selectServer() { - final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.get()); - builder.setMessage("Select a server"); - - builder.setPositiveButton("Main", new DialogInterface.OnClickListener() { - public void onClick(final DialogInterface dialog, final int id) { - testing = false; - dialog.cancel(); - onSelectServer(); - } - }); - - builder.setNegativeButton("Testing", new DialogInterface.OnClickListener() { - public void onClick(final DialogInterface dialog, final int id) { - testing = true; - clientUrlSuffix = "testclient"; - dialog.cancel(); - onSelectServer(); - } - }); - - final AlertDialog selectServer = builder.create(); - selectServer.show(); - } - - private void onSelectServer() { - // remove splash image - setSplashResource(android.R.color.transparent); - - final String custom_page = checkCustomServer(); - if (custom_page != null) { - DebugLog.debug("Connecing to custom page: " + custom_page); - - loadUrl(custom_page); - } else { - // initial page - loadUrl(defaultServer + "account/mycharacters.html"); - - if (testing) { - DebugLog.debug("Connecting to test server"); - } else { - DebugLog.debug("Connecting to main server"); - - Notifier.get().showMessage("CAUTION: This software is in early development and not recommended" - + " for use on the main server. Proceed with caution.", false); - } - } - - currentPage = PageId.OTHER; - // hide menu after exiting title screen - Menu.get().hide(); - } - - private String checkCustomServer() { - final String cs = PreferencesActivity.getString("client_url", "").trim(); - - if (cs.equals("")) { - return null; - } - - return cs; - } - - /** - * Checks if a URL is a link to one of the web clients. - * - * @param url - * URL to be checked. - * @return - * true if url links to "client" or "testclient". - */ - private boolean isClientUrl(final String url) { - final String custom_client = PreferencesActivity.getString("client_url").trim(); - if (!custom_client.equals("")) { - return url.contains(custom_client); - } - - return url.contains("stendhalgame.org/client/") - || url.contains("stendhalgame.org/testclient/"); - } - - /** - * Formats client URL for currently selected server. - * - * @param url - * URL to be checked. - * @return - * URL to be loaded. - */ - private String checkClientUrl(String url) { - if (isClientUrl(url)) { - String replaceSuffix = "/testclient/"; - if (testing) { - replaceSuffix = "/client/"; - } - - url = url.replace(replaceSuffix, "/" + clientUrlSuffix + "/"); - } - - return url; - } - - /** - * Checks if requested URL is whitelisted to be opened within WebView client. - * - * @param url - * URL to be checked. - * @return - * true if URL is under domain stendhalgame.org or localhost. - */ - private boolean isInternalUrl(final String url) { - final String domain = getDomain(url); - final String cs = checkCustomServer(); - - if (cs != null) { - return domain.startsWith(getDomain(cs)); - } else { - return domain.startsWith("stendhalgame.org") || domain.startsWith("localhost"); - } - } - - private String getDomain(final String url) { - return url.replaceAll("^https://", "").replaceAll("^http://", "") - .replaceAll("^www\\.", ""); - } - - public static boolean onTitleScreen() { - return currentPage == PageId.TITLE; - } - - public static boolean isGameActive() { - return currentPage == PageId.WEBCLIENT; - } - - public static PageId getCurrentPageId() { - return currentPage; - } - - public static void playTitleMusic(String musicId) { - if (musicId == null) { - musicId = PreferencesActivity.getString("song_list"); - } - - int id = R.raw.title_01; - switch (musicId) { - case "title_02": - id = R.raw.title_02; - break; - case "title_03": - id = R.raw.title_03; - break; - case "title_04": - id = R.raw.title_04; - break; - case "title_05": - id = R.raw.title_05; - break; - } - - DebugLog.debug("playing music: " + musicId); - - MusicPlayer.playMusic(id, true); - } - - public static void playTitleMusic() { - playTitleMusic(null); - } - - /** - * Checks if this is a debug build. - * - * @return - * true if debug flag set. - */ - private boolean debugEnabled() { - if (debugging != null) { - return debugging; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - debugging = (MainActivity.get().getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } else { - debugging = false; - } - - return debugging; - } -} diff --git a/android/app/src/main/java/org/arianne/stendhal/client/DownloadHandler.java b/android/app/src/main/java/org/arianne/stendhal/client/DownloadHandler.java deleted file mode 100644 index e1008543aa..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/DownloadHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client; - -import android.net.Uri; -import android.os.Environment; -import android.util.Base64; - -import java.io.File; -import java.io.FileOutputStream; - -import marauroa.common.Pair; - - -/** - * The main purpose of this class is to handle downloading screenshots - * created by the web client page. - */ -public class DownloadHandler { - - public Pair download(final String url, final String mimetype) { - final Pair res = new Pair(false, null); - - final Uri uri = Uri.parse(url); - final String scheme = uri.getScheme(); - final String storageState = Environment.getExternalStorageState(); - - if (!ClientView.get().isGameActive()) { - res.setSecond("downloading from this page not supported"); - } else if (!"data".equals(scheme)) { - res.setSecond("download type \"" + scheme + "\" not supported"); - } else if (!"image/png".equals(mimetype) || !url.startsWith("data:image/png;base64,")) { - res.setSecond("mimetype not supported: " + mimetype); - } else if (!Environment.MEDIA_MOUNTED.equals(storageState)) { - res.setSecond("storage not available for writing (state: " + storageState + ")"); - } else { - final File targetDir = new File(Environment.getExternalStorageDirectory() - + "/Pictures/Screenshots"); - // TODO: format output name using YYYYMMDD_HH.MM.SS.MS - final String targetName = "stendhal_" + System.currentTimeMillis() + ".png"; - - DebugLog.debug("Saving screenshot: " + targetDir.getPath() - + "/" + targetName + " (" + mimetype + ")"); - - String msg; - String stacktrace = null; - try { - if (!targetDir.exists()) { - targetDir.mkdirs(); - } - - final byte[] data = Base64.decode(url.split("base64,")[1], Base64.DEFAULT); - final FileOutputStream fos = new FileOutputStream(new File(targetDir, targetName)); - fos.write(data); - fos.close(); - - res.setFirst(true); - msg = "saved screenshot to " + targetDir.getPath() + "/" + targetName; - } catch (final java.lang.NoClassDefFoundError e) { - msg = "an error occurred while decoding data (see debug log for more info)"; - stacktrace = stackTraceToString(e); - } catch (java.io.IOException e) { - msg = "an error occurred while attempting to write file (see debug log for more info)"; - stacktrace = stackTraceToString(e); - } - - res.setSecond(msg); - if (stacktrace != null) { - DebugLog.error(stacktrace.toString()); - } - } - - return res; - } - - /** - * Converts an exception stacktrace to string. - * - * @param e - * The error with stacktrace information. - * @return - * String formatted stacktrace. - */ - private String stackTraceToString(final Throwable e) { - final StringBuilder sb = new StringBuilder(); - sb.append(e.toString()); - for (final StackTraceElement ste: e.getStackTrace()) { - if (sb.length() > 0) { - sb.append("\n"); - } - sb.append(ste.toString()); - } - - return sb.toString(); - } -} diff --git a/android/app/src/main/java/org/arianne/stendhal/client/MainActivity.java b/android/app/src/main/java/org/arianne/stendhal/client/MainActivity.java deleted file mode 100644 index 519ba2e00b..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/MainActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client; - -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.constraintlayout.widget.ConstraintLayout; - -import org.arianne.stendhal.client.input.DPad; -import org.arianne.stendhal.client.input.DPadArrows; -import org.arianne.stendhal.client.input.DPadJoy; - - -public class MainActivity extends AppCompatActivity { - - private static MainActivity instance; - - private ConstraintLayout layout; - private ClientView client; - private Menu menu; - - - public static MainActivity get() { - return instance; - } - - @Override - protected void onCreate(final Bundle savedInstanceState) { - try { - super.onCreate(savedInstanceState); - instance = this; - - // initialize debug logging mechanism - DebugLog.init(getExternalFilesDir(null), this); - - setContentView(R.layout.activity_main); - layout = (ConstraintLayout) findViewById(R.id.content); - menu = new Menu(this); - client = (ClientView) findViewById(R.id.clientWebView); - - // initialize d-pads - final DPad arrowPad = DPadArrows.get(); - final DPad joyPad = DPadJoy.get(); - - if (PreferencesActivity.getBoolean("dpad_joy", true)) { - DPad.setCurrentPad(joyPad); - } else { - DPad.setCurrentPad(arrowPad); - } - - layout.addView(arrowPad.getLayout()); - layout.addView(joyPad.getLayout()); - - client.loadTitleScreen(); - } catch (final Exception e) { - e.printStackTrace(); - DebugLog.error(e.toString()); - DebugLog.error("// -- //"); - for (final StackTraceElement ste: e.getStackTrace()) { - DebugLog.error(ste.toString()); - } - DebugLog.error("// -- //"); - Notifier.get().showPrompt( - "An unhandled exception has occurred: " + e.getMessage() - + "\n\nYou can report this error at: https://stendhalgame.org/development/bug.html", - new Notifier.Action() { - protected void onCall() { - finish(); - } - }); - } - } - - @Override - public void onBackPressed() { - menu.toggleVisibility(); - } - - /** - * Attempts to connect to client host. - */ - public void loadLogin() { - client.loadLogin(); - } - - /** - * Opens a dialog confirm exiting activity. - */ - public void onRequestQuit() { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage("Quit Stendhal?"); - - builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { - public void onClick(final DialogInterface dialog, final int id) { - finish(); - } - }); - - builder.setNegativeButton("No", new DialogInterface.OnClickListener() { - public void onClick(final DialogInterface dialog, final int id) { - dialog.cancel(); - } - }); - - final AlertDialog confirmQuit = builder.create(); - confirmQuit.show(); - } - - @Override - protected void onResume() { - super.onResume(); - - DPadArrows.get().onRefreshView(); - DPadJoy.get().onRefreshView(); - } - - /** - * Updates direction pad position when screen orientation changes. - */ - @Override - public void onConfigurationChanged(final Configuration config) { - super.onConfigurationChanged(config); - - DPadArrows.get().onRefreshView(); - DPadJoy.get().onRefreshView(); - } - - @Override - public void finish() { - DebugLog.debug(MainActivity.class.getName() + ".finish() called"); - - super.finish(); - } - - @Override - protected void onDestroy() { - DebugLog.debug(MainActivity.class.getName() + ".onDestroy() called"); - - super.onDestroy(); - } - - public void showSettings() { - startActivity(new Intent(MainActivity.this, PreferencesActivity.class)); - } -} diff --git a/android/app/src/main/java/org/arianne/stendhal/client/input/DPad.java b/android/app/src/main/java/org/arianne/stendhal/client/input/DPad.java deleted file mode 100644 index ab9a32b252..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/input/DPad.java +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client.input; - -import android.view.View; -import androidx.constraintlayout.widget.ConstraintLayout; - -import marauroa.common.Pair; - - -public abstract class DPad { - - public static enum Dir { - NONE, - LEFT, - RIGHT, - UP, - DOWN; - } - - private static DPad currentPad = null; - - - public abstract ConstraintLayout getLayout(); - - public abstract void setVisibility(final int vis); - - public abstract boolean isVisible(); - - public void show() { - setVisibility(View.VISIBLE); - } - - public void hide() { - setVisibility(View.GONE); - } - - public abstract void setPosition(final int x, final int y); - - public abstract Pair getPosition(); - - public abstract Pair getSize(); - - public abstract void onRefreshView(); - - public static void setCurrentPad(final DPad pad) { - currentPad = pad; - } - - public static DPad getCurrentPad() { - return currentPad; - } -} diff --git a/android/app/src/main/java/org/arianne/stendhal/client/input/DPadArrows.java b/android/app/src/main/java/org/arianne/stendhal/client/input/DPadArrows.java deleted file mode 100644 index c64b9b57a5..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/input/DPadArrows.java +++ /dev/null @@ -1,253 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client.input; - -import android.content.Context; -import android.graphics.Point; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.widget.ImageView; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams; - -import java.util.LinkedList; -import java.util.List; - -import marauroa.common.Pair; -import org.arianne.stendhal.client.DebugLog; -import org.arianne.stendhal.client.MainActivity; -import org.arianne.stendhal.client.PreferencesActivity; -import org.arianne.stendhal.client.R; - - -/** - * A directional pad on the screen. - * - * TODO: Option to use joystick style d-pad. Currently setting "dpad_joy" - * is disabled in preferences manager. - */ -public class DPadArrows extends DPad { - - private static DPadArrows instance; - - private static Context ctx; - - private final List arrows; - private final ConstraintLayout layout; - - - public static DPadArrows get() { - if (instance == null) { - instance = new DPadArrows(); - } - - return instance; - } - - private DPadArrows() { - ctx = (Context) MainActivity.get(); - - arrows = new LinkedList<>(); - layout = new ConstraintLayout(ctx); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - final ArrowView arrow_l = new ArrowView(ctx, Dir.LEFT); - final ArrowView arrow_r = new ArrowView(ctx, Dir.RIGHT); - final ArrowView arrow_u = new ArrowView(ctx, Dir.UP); - final ArrowView arrow_d = new ArrowView(ctx, Dir.DOWN); - - arrows.add(arrow_l); - arrows.add(arrow_r); - arrows.add(arrow_u); - arrows.add(arrow_d); - - for (final ArrowView av: arrows) { - layout.addView(av); - } - - // hidden by default - hide(); - } - - public ConstraintLayout getLayout() { - return layout; - } - - public void setVisibility(final int vis) { - for (final ArrowView av: arrows) { - av.setVisibility(vis); - } - } - - public boolean isVisible() { - for (final ArrowView av: arrows) { - if (av.getVisibility() != View.VISIBLE) { - return false; - } - } - - return true; - } - - public void setPosition(final int x, final int y) { - ArrowView av = arrows.get(0); - av.setX(x); - av.setY(y + 60); - - av = arrows.get(1); - av.setX(x + 150); - av.setY(y + 60); - - av = arrows.get(2); - av.setX(x + 60); - av.setY(y); - - av = arrows.get(3); - av.setX(x + 60); - av.setY(y + 150); - } - - private int getX() { - return (int) arrows.get(0).getX(); - } - - private int getY() { - return (int) arrows.get(2).getY(); - } - - public Pair getPosition() { - return new Pair(getX(), getY()); - } - - private int getWidth() { - final ArrowView aRight = arrows.get(1); - final int absX = ((int) aRight.getX()) + aRight.getWidth(); - - return absX - getX(); - } - - private int getHeight() { - final ArrowView aDown = arrows.get(3); - final int absY = ((int) aDown.getY()) + aDown.getHeight(); - - return absY - getY(); - } - - public Pair getSize() { - return new Pair(getWidth(), getHeight()); - } - - public void onRefreshView() { - final Point size = new Point(); - final Display disp = MainActivity.get().getWindowManager().getDefaultDisplay(); - disp.getSize(size); - - final PreferencesActivity prefs = PreferencesActivity.get(); - final int x = prefs.getInt("dpad_offset_x", 100); - final int y = size.y - prefs.getInt("dpad_offset_y", 300); - - setPosition(x, y); - - if (isVisible()) { - DebugLog.debug("DPadArrows: pos (" + getX() + "," + getY() + "), size (" - + getWidth() + "," + getHeight() + ")"); - DebugLog.debug("DPadArrows: left arrow: x (" + arrows.get(0).getX() + "), width (" - + arrows.get(0).getWidth() + ")"); - DebugLog.debug("DPadArrows: down arrow: y (" + arrows.get(3).getY() + "), height (" - + arrows.get(3).getHeight() + ")"); - } - } - - - private static class ArrowView extends ImageView { - - private final Dir dir; - private final int id; - private final int id_active; - - private boolean keyDown = false; - - - public ArrowView(final Context ctx, final Dir dir) { - super(ctx); - - this.dir = dir; - - switch (dir) { - case LEFT: - id = R.drawable.dpad_arrow_left; - id_active = R.drawable.dpad_arrow_left_active; - break; - case RIGHT: - id = R.drawable.dpad_arrow_right; - id_active = R.drawable.dpad_arrow_right_active; - break; - case UP: - id = R.drawable.dpad_arrow_up; - id_active = R.drawable.dpad_arrow_up_active; - break; - default: - id = R.drawable.dpad_arrow_down; - id_active = R.drawable.dpad_arrow_down_active; - break; - } - - setBackgroundColor(android.graphics.Color.TRANSPARENT); - setImageResource(id); - - setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent event) { - final int action = event.getAction(); - - Integer newAction = null; - Integer keyCode = null; - - if (action == MotionEvent.ACTION_DOWN && !keyDown) { - setImageResource(id_active); - newAction = KeyEvent.ACTION_DOWN; - keyDown = true; - } else if (action == MotionEvent.ACTION_UP && keyDown) { - setImageResource(id); - newAction = KeyEvent.ACTION_UP; - keyDown = false; - } - - if (newAction != null) { - - switch (dir) { - case LEFT: - keyCode = KeyEvent.KEYCODE_DPAD_LEFT; - break; - case RIGHT: - keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - case UP: - keyCode = KeyEvent.KEYCODE_DPAD_UP; - break; - case DOWN: - keyCode = KeyEvent.KEYCODE_DPAD_DOWN; - break; - } - - MainActivity.get().dispatchKeyEvent(new KeyEvent(newAction, keyCode)); - return true; // consume event - } - - return false; - } - }); - } - } -} diff --git a/android/app/src/main/java/org/arianne/stendhal/client/input/DPadJoy.java b/android/app/src/main/java/org/arianne/stendhal/client/input/DPadJoy.java deleted file mode 100644 index 08445530a3..0000000000 --- a/android/app/src/main/java/org/arianne/stendhal/client/input/DPadJoy.java +++ /dev/null @@ -1,332 +0,0 @@ -/*************************************************************************** - * Copyright © 2022 - Arianne * - *************************************************************************** - *************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -package org.arianne.stendhal.client.input; - -import android.content.Context; -import android.graphics.Point; -import android.view.Display; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintLayout.LayoutParams; - -import java.util.ArrayList; -import java.util.List; - -import marauroa.common.Pair; -import org.arianne.stendhal.client.DebugLog; -import org.arianne.stendhal.client.MainActivity; -import org.arianne.stendhal.client.PreferencesActivity; -import org.arianne.stendhal.client.R; - - -public class DPadJoy extends DPad { - - private static DPadJoy instance; - - private static Context ctx; - - private static ConstraintLayout layout; - - private static OuterButton outerButton; - private static int sensitivity; - - - public static DPadJoy get() { - if (instance == null) { - instance = new DPadJoy(); - } - - return instance; - } - - private DPadJoy() { - ctx = (Context) MainActivity.get(); - layout = new ConstraintLayout(ctx); - layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - outerButton = new OuterButton(ctx); - outerButton.addToLayout(); - - setSensitivity(PreferencesActivity.getInt("joypad_sensitivity", 25)); - - // hidden by default - hide(); - } - - public ConstraintLayout getLayout() { - return layout; - } - - public void setVisibility(final int vis) { - outerButton.setVisibility(vis); - } - - public boolean isVisible() { - return outerButton.isVisible(); - } - - public void setPosition(final int x, final int y) { - outerButton.setPosition(x, y); - } - - private int getX() { - return (int) outerButton.getX(); - } - - private int getY() { - return (int) outerButton.getY(); - } - - public Pair getPosition() { - return new Pair(getX(), getY()); - } - - private int getWidth() { - return outerButton.getWidth(); - } - - private int getHeight() { - return outerButton.getHeight(); - } - - public Pair getSize() { - return new Pair(getWidth(), getHeight()); - } - - public void setSensitivity(final int value) { - sensitivity = value; - } - - public void onRefreshView() { - final Point size = new Point(); - final Display disp = MainActivity.get().getWindowManager().getDefaultDisplay(); - disp.getSize(size); - - final PreferencesActivity prefs = PreferencesActivity.get(); - final int x = prefs.getInt("dpad_offset_x", 100); - final int y = size.y - prefs.getInt("dpad_offset_y", 300); - - setPosition(x, y); - - if (isVisible()) { - DebugLog.debug("DPadJoy: pos (" + getX() + "," + getY() + "), size (" - + getWidth() + "," + getHeight() + ")"); - } - } - - - private static class OuterButton extends JoyPadButton { - - private final InnerButton innerButton; - - private DPad.Dir currentDir = DPad.Dir.NONE; - private boolean keyDown = false; - private final List pressedKeys; - - - public OuterButton(final Context ctx) { - super(ctx); - - pressedKeys = new ArrayList<>(); - - setBackgroundColor(android.graphics.Color.TRANSPARENT); - setImageResource(R.drawable.dpad_circle_outer); - - innerButton = new InnerButton(ctx); - innerButton.setBackgroundColor(android.graphics.Color.TRANSPARENT); - innerButton.setImageResource(R.drawable.dpad_circle_inner); - - setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(final View view, final MotionEvent event) { - final int action = event.getAction(); - - DebugLog.debug("joypad action: " + action); - - if (action == MotionEvent.ACTION_DOWN && !keyDown) { - keyDown = true; - innerButton.setImageResource(R.drawable.dpad_circle_inner_active); - innerButton.setRelativeX(event.getX()); - innerButton.setRelativeY(event.getY()); - - final DPad.Dir newDir = getTouchDirection(); - if (newDir != currentDir) { - currentDir = newDir; - onDirectionChange(KeyEvent.ACTION_DOWN); - } - } else if (action == MotionEvent.ACTION_UP && keyDown) { - keyDown = false; - innerButton.setImageResource(R.drawable.dpad_circle_inner); - innerButton.centerOn((JoyPadButton) view); - - clearPresses(); - currentDir = DPad.Dir.NONE; - } else if (action == MotionEvent.ACTION_MOVE && keyDown) { - innerButton.setRelativeX(event.getX()); - innerButton.setRelativeY(event.getY()); - - final DPad.Dir newDir = getTouchDirection(); - if (newDir != currentDir) { - currentDir = newDir; - onDirectionChange(KeyEvent.ACTION_DOWN); - } - } - - return true; - } - }); - } - - public void addToLayout() { - layout.addView(this); - layout.addView(innerButton); - - // FIXME: inner button not centering at startup - innerButton.centerOn(this); - } - - @Override - public void setVisibility(final int vis) { - innerButton.setVisibility(vis); - super.setVisibility(vis); - } - - public boolean isVisible() { - return getVisibility() == View.VISIBLE - && innerButton.getVisibility() == View.VISIBLE; - } - - public void setPosition(final int x, final int y) { - setX(x); - setY(y); - - innerButton.centerOn(this); - } - - /** - * Determines pressed direction based on inner button position - * relative to outer button position. - */ - private DPad.Dir getTouchDirection() { - final float outerX = getCenterX(); - final float outerY = getCenterY(); - final float innerX = innerButton.getCenterX(); - final float innerY = innerButton.getCenterY(); - final float offsetX = outerX - innerX; - final float offsetY = outerY - innerY; - - final int absX = (int) Math.abs(offsetX); - final int absY = (int) Math.abs(offsetY); - - if (absX < sensitivity && absY < sensitivity) { - return DPad.Dir.NONE; - } - - // horizontal directions take precedence if they are the same as vertical - if (absX >= absY) { - if (innerX < outerX) { - return DPad.Dir.LEFT; - } else if (innerX > outerX) { - return DPad.Dir.RIGHT; - } - } else { - if (innerY < outerY) { - return DPad.Dir.UP; - } else if (innerY > outerY) { - return DPad.Dir.DOWN; - } - } - - return DPad.Dir.NONE; - } - - private void clearPresses() { - for (final int key: pressedKeys) { - MainActivity.get().dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, key)); - } - } - - /** - * Sends a direction key press event to client. - */ - private void onDirectionChange(final int action) { - Integer keyCode = null; - - switch (currentDir) { - case LEFT: - keyCode = KeyEvent.KEYCODE_DPAD_LEFT; - break; - case RIGHT: - keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - case UP: - keyCode = KeyEvent.KEYCODE_DPAD_UP; - break; - case DOWN: - keyCode = KeyEvent.KEYCODE_DPAD_DOWN; - break; - } - - if (keyCode != null) { - // clear pressed keys before dispatching new key press - clearPresses(); - - if (action == KeyEvent.ACTION_DOWN) { - MainActivity.get().dispatchKeyEvent(new KeyEvent(action, keyCode)); - pressedKeys.add(keyCode); - } - } - } - } - - - private static class InnerButton extends JoyPadButton { - - public InnerButton(final Context ctx) { - super(ctx); - } - - /** - * Sets X coordinate relative to OuterButton. - */ - public void setRelativeX(float x) { - x = x + outerButton.getX(); - - final float maxL = outerButton.getX(); - final float maxR = maxL + outerButton.getWidth(); - - // don't move outside bounds of outer button - if (x >= maxL && x <= maxR) { - // use circle center - setX(x - (getWidth() / 2)); - } - } - - /** - * Sets Y coordinate relative to OuterButton. - */ - public void setRelativeY(float y) { - y = y + outerButton.getY(); - - final float maxT = outerButton.getY(); - final float maxB = maxT + outerButton.getHeight(); - - // don't move outside bounds of outer button - if (y >= maxT && y <= maxB) { - // use circle center - setY(y - (getHeight() / 2)); - } - } - } -} diff --git a/android/app/src/main/res/drawable/dpad_arrow_down.png b/android/app/src/main/res/drawable/dpad_arrow_down.png deleted file mode 100644 index 1a133e2f7b..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_down.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_down_active.png b/android/app/src/main/res/drawable/dpad_arrow_down_active.png deleted file mode 100644 index 878c087459..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_down_active.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_left.png b/android/app/src/main/res/drawable/dpad_arrow_left.png deleted file mode 100644 index ec1f361e05..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_left.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_left_active.png b/android/app/src/main/res/drawable/dpad_arrow_left_active.png deleted file mode 100644 index 0d160a5c6f..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_left_active.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_right.png b/android/app/src/main/res/drawable/dpad_arrow_right.png deleted file mode 100644 index 4a4c819304..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_right.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_right_active.png b/android/app/src/main/res/drawable/dpad_arrow_right_active.png deleted file mode 100644 index e56cfb7697..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_right_active.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_up.png b/android/app/src/main/res/drawable/dpad_arrow_up.png deleted file mode 100644 index 9a74322f3f..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_up.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_arrow_up_active.png b/android/app/src/main/res/drawable/dpad_arrow_up_active.png deleted file mode 100644 index 935508ce23..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_arrow_up_active.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_circle_inner.png b/android/app/src/main/res/drawable/dpad_circle_inner.png deleted file mode 100644 index dd82532d29..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_circle_inner.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_circle_inner_active.png b/android/app/src/main/res/drawable/dpad_circle_inner_active.png deleted file mode 100644 index fe204db8ac..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_circle_inner_active.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/dpad_circle_outer.png b/android/app/src/main/res/drawable/dpad_circle_outer.png deleted file mode 100644 index 3e6dc4fd20..0000000000 Binary files a/android/app/src/main/res/drawable/dpad_circle_outer.png and /dev/null differ diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index f7ec721633..0000000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - -