diff --git a/CMakeLists.txt b/CMakeLists.txt index 552a4d9e4..0efb55525 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,11 @@ set(CMAKE_RELWITHDEBINFO_POSTFIX "") set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_SKIP_RPATH ON) -add_executable(${PROJECT_NAME}) +if(ANDROID) + add_library(${PROJECT_NAME} SHARED) +else() + add_executable(${PROJECT_NAME}) +endif() if(MSVC) add_definitions(-D_USE_MATH_DEFINES) @@ -59,6 +63,12 @@ elseif(IOS) MACOSX_BUNDLE_BUNDLE_VERSION 1 MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer") +elseif(ANDROID) + # Android build configuration + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + # Build as shared library for Android + set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_NAME "opengothic") elseif(APPLE) enable_language(OBJCXX) endif() @@ -96,6 +106,12 @@ endif() if(WIN32) target_link_libraries(${PROJECT_NAME} shlwapi DbgHelp) +elseif(ANDROID) + # Add android_native_app_glue + set(ANDROID_NATIVE_APP_GLUE_DIR "${ANDROID_NDK}/sources/android/native_app_glue") + target_sources(${PROJECT_NAME} PRIVATE "${ANDROID_NATIVE_APP_GLUE_DIR}/android_native_app_glue.c") + target_include_directories(${PROJECT_NAME} PRIVATE "${ANDROID_NATIVE_APP_GLUE_DIR}") + target_link_libraries(${PROJECT_NAME} android log) elseif(UNIX) target_link_libraries(${PROJECT_NAME} -lpthread -ldl) endif() diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..8f1312998 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,66 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ +/build/ +/app/build/ +**/build/ + +# Local configuration file +local.properties +/local.properties + +# Android Studio +*.iml +.idea/ +/.idea/ +*.hprof +captures/ +/captures/ + +# NDK +.externalNativeBuild/ +.cxx/ +/app/.cxx/ +obj/ + +# Keystore files +*.jks +*.keystore + +# Log files +*.log + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Signing config +signing.properties + +# Proguard +proguard/ + +# Misc +*.swp +*~ diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 000000000..0912e4499 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.example.opengothic' + compileSdk 35 + + defaultConfig { + applicationId "com.example.opengothic" + minSdk 24 + targetSdk 35 + versionCode 1 + versionName "1.0" + + externalNativeBuild { + cmake { + cppFlags '-std=c++20 -fvisibility=hidden -ffunction-sections -fdata-sections' + arguments '-DANDROID_STL=c++_static', + '-DBUILD_SHARED_LIBS=ON', + '-DCMAKE_BUILD_TYPE=Release' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + splits { + abi { + enable true + reset() + include 'arm64-v8a', 'x86_64' + universalApk false // Set true if you also want a fat APK with all ABIs + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + version '3.22.1' + } + } + + buildFeatures { + prefab true + } + + packagingOptions { + jniLibs { + // Debug symbols stripped automatically in release + } + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.games:games-activity:3.0.5' +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 000000000..b17b723bb --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,8 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in the Android SDK. + +# Keep native methods +-keepclasseswithmembernames class * { + native ; +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..cdbbe902c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 000000000..ab9443729 --- /dev/null +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.22.1) + +project(opengothic) + +# Path to the main OpenGothic project (parent of android folder) +set(OPENGOTHIC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../..") + +# Include the main project's CMakeLists.txt +# The main CMakeLists.txt already handles ANDROID builds and creates a shared library +add_subdirectory(${OPENGOTHIC_ROOT} ${CMAKE_CURRENT_BINARY_DIR}/opengothic) + +# The main project creates Gothic2Notr target, we need to rename it to opengothic +# for Android to find it as libopengothic.so +set_target_properties(Gothic2Notr PROPERTIES OUTPUT_NAME "opengothic") diff --git a/android/app/src/main/res/ic_launcher.webp b/android/app/src/main/res/ic_launcher.webp new file mode 100644 index 000000000..c209e78ec Binary files /dev/null and b/android/app/src/main/res/ic_launcher.webp differ diff --git a/android/app/src/main/res/ic_launcher.xml b/android/app/src/main/res/ic_launcher.xml new file mode 100644 index 000000000..6f3b755bf --- /dev/null +++ b/android/app/src/main/res/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/ic_launcher_round.webp b/android/app/src/main/res/ic_launcher_round.webp new file mode 100644 index 000000000..b2dfe3d1b Binary files /dev/null and b/android/app/src/main/res/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/ic_launcher_round.xml b/android/app/src/main/res/ic_launcher_round.xml new file mode 100644 index 000000000..6f3b755bf --- /dev/null +++ b/android/app/src/main/res/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 000000000..c209e78ec Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 000000000..b2dfe3d1b Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 000000000..4f0f1d64e Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 000000000..62b611da0 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 000000000..948a3070f Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..1b9a6956b Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 000000000..28d4b77f9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9287f5083 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 000000000..aa7d6427e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 000000000..9126ae37c Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values-night/themes.xml b/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 000000000..11864d433 --- /dev/null +++ b/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..7aaab4ca5 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FF000000 + #FFFFFFFF + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..6642ae14a --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + OpenGothic + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 000000000..75a136fbb --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + + diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 000000000..a608293f2 --- /dev/null +++ b/android/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 000000000..e84e7f461 --- /dev/null +++ b/android/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..cfaef4e29 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '8.7.3' apply false + id 'org.jetbrains.kotlin.android' version '2.0.0' apply false +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..2cf094bea --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,5 @@ +# Project-wide Gradle settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +android.useAndroidX=true +kotlin.code.style=official +android.nonTransitiveRClass=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..8bdaf60c7 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..d6e308a63 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 000000000..ef07e0162 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 000000000..db3a6ac20 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..dec7c2b61 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "OpenGothic" +include ':app' diff --git a/game/commandline.cpp b/game/commandline.cpp index eda2039ba..6b4639535 100644 --- a/game/commandline.cpp +++ b/game/commandline.cpp @@ -225,13 +225,23 @@ std::u16string CommandLine::nestedPath(const std::initializer_list main; - + /// Reverse direction (e.g. S or Down arrow) std::array reverse; @@ -108,7 +109,7 @@ class PlayerControl final { } return false; } - + /// Is any key pressed that activates the reverse direction /// (e.g. S or Down arrow in Forward-Backward axis) auto anyReverse() const -> bool { @@ -120,7 +121,7 @@ class PlayerControl final { }; struct MovementStatus { - AxisStatus forwardBackward; + AxisStatus forwardBackward; AxisStatus strafeRightLeft; AxisStatus turnRightLeft; @@ -131,7 +132,7 @@ class PlayerControl final { this->turnRightLeft.reset(); } } movement; - + bool ctrl[Action::Last]={}; bool wctrl[WeaponAction::Last]={}; bool actrl[7]={}; @@ -142,6 +143,8 @@ class PlayerControl final { Focus currentFocus; float rotMouse=0; float rotMouseY=0; + float gamepadLX=0; + float gamepadLY=0; bool casting = false; size_t pickLockProgress = 0; @@ -180,17 +183,17 @@ class PlayerControl final { ////////////////////////////////// auto wantsToMoveForward() const -> bool { - return movement.forwardBackward.value() > 0.f; + return movement.forwardBackward.value() > 0.f || gamepadLY < -0.2f; } auto wantsToMoveBackward() const -> bool { - return movement.forwardBackward.value() < 0.f; + return movement.forwardBackward.value() < 0.f || gamepadLY > 0.2f; } auto wantsToStrafeRight() const -> bool { - return movement.strafeRightLeft.value() > 0.f; + return movement.strafeRightLeft.value() > 0.f || gamepadLX > 0.2f; } auto wantsToStrafeLeft() const -> bool { - return movement.strafeRightLeft.value() < 0.f; + return movement.strafeRightLeft.value() < 0.f || gamepadLX < -0.2f; } auto wantsToTurnRight() const -> bool { diff --git a/game/graphics/renderer.cpp b/game/graphics/renderer.cpp index ea716436b..3b4a108eb 100644 --- a/game/graphics/renderer.cpp +++ b/game/graphics/renderer.cpp @@ -193,6 +193,10 @@ void Renderer::setupSettings() { settings.vidResIndex = Gothic::inst().settingsGetF("INTERNAL","vidResIndex"); settings.aaEnabled = (Gothic::options().aaPreset>0) && (settings.vidResIndex==0); + settings.shadowResolution = uint32_t(Gothic::settingsGetI("INTERNAL","shadowResolution")); + if(settings.shadowResolution == 0) + settings.shadowResolution = 2048; // fallback default + // direct lighting if(settings.rtsmEnabled) shadow.directLightPso = &shaders.rtsmDirectLight; diff --git a/game/main.cpp b/game/main.cpp index de00118e9..42603bc74 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -14,7 +14,7 @@ #include #endif -#if defined(__IOS__) +#if defined(__IOS__) || defined(__ANDROID__) #include "utils/installdetect.h" #endif @@ -53,7 +53,7 @@ std::unique_ptr mkApi(const CommandLine& g) { break; #endif case CommandLine::Vulkan: -#if !defined(__APPLE__) +#if !defined(__APPLE__) || defined(__ANDROID__) return std::make_unique(flg); #else break; @@ -73,6 +73,13 @@ int main(int argc,const char** argv) { auto appdir = InstallDetect::applicationSupportDirectory(); std::filesystem::current_path(appdir); } +#elif defined(__ANDROID__) + { + auto appdir = InstallDetect::applicationSupportDirectory(); + if(!appdir.empty()) { + std::filesystem::current_path(appdir); + } + } #endif try { diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index 34985d74e..929cea27d 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -26,6 +26,8 @@ #include "commandline.h" #include "gothic.h" +#include + using namespace Tempest; MainWindow::MainWindow(Device& device) @@ -281,8 +283,17 @@ void MainWindow::paintEvent(PaintEvent& event) { void MainWindow::resizeEvent(SizeEvent&) { for(auto& i:fence) i.wait(); + + auto rectBefore = SystemApi::windowClientRect(hwnd()); + Tempest::Log::i("MainWindow::resizeEvent: Starting resize - window: ", rectBefore.w, "x", rectBefore.h); + swapchain.reset(); renderer.resetSwapchain(); + + auto rectAfter = SystemApi::windowClientRect(hwnd()); + Tempest::Log::i("MainWindow::resizeEvent: After swapchain reset - window: ", rectAfter.w, "x", rectAfter.h); + Tempest::Log::i("MainWindow::resizeEvent: Swapchain size: ", swapchain.w(), "x", swapchain.h()); + if(auto camera = Gothic::inst().camera()) camera->setViewport(swapchain.w(),swapchain.h()); @@ -291,6 +302,8 @@ void MainWindow::resizeEvent(SizeEvent&) { setCursorPosition(rect.w/2,rect.h/2); setCursorShape(fs ? CursorShape::Hidden : CursorShape::Arrow); dMouse = Point(); + + Tempest::Log::i("MainWindow::resizeEvent: Resize completed"); } void MainWindow::mouseDownEvent(MouseEvent &event) { @@ -369,6 +382,42 @@ void MainWindow::tickMouse(uint64_t dt) { dMouse = Point(); } +void MainWindow::tickGamepad() { + auto gp = SystemApi::gamepadState(); + if(!gp.connected) + return; + + auto camera = Gothic::inst().camera(); + if(dialogs.hasContent() || Gothic::inst().isPause() || camera==nullptr || camera->isCutscene()) + return; + + const float deadzone = 0.15f; + const float sensitivity = 15.0f; + + float rx = gp.rightStickX; + float ry = gp.rightStickY; + + if(std::abs(rx) < deadzone) rx = 0.0f; + if(std::abs(ry) < deadzone) ry = 0.0f; + + if(rx != 0.0f || ry != 0.0f) { + PointF dp(ry * sensitivity, -rx * sensitivity); + camera->onRotateMouse(dp); + + if(!inventory.isActive()) { + player.onRotateMouse(-dp.y); + } + } + + float lx = gp.leftStickX; + float ly = gp.leftStickY; + + if(std::abs(lx) < deadzone) lx = 0.0f; + if(std::abs(ly) < deadzone) ly = 0.0f; + + player.setGamepadAxis(lx, ly); + } + void MainWindow::onSettings() { auto zMaxFps = Gothic::options().fpsLimit; if(zMaxFps<=0) @@ -529,6 +578,7 @@ void MainWindow::keyUpEvent(KeyEvent &event) { if(auto pl = Gothic::inst().player()) rootMenu.setPlayer(*pl); clearInput(); + //event.accept(); } else if(act==KeyCodec::Inventory && !dialogs.isActive()) { if(inventory.isActive()) { @@ -892,6 +942,7 @@ uint64_t MainWindow::tick() { else if(runtimeMode==R_Suspended) { auto camera = Gothic::inst().camera(); if(camera!=nullptr && camera->isFree()) { + tickGamepad(); tickMouse(dt); } update(); @@ -907,7 +958,8 @@ uint64_t MainWindow::tick() { ;//clearInput(); if(document.isActive()) clearInput(); - tickMouse(dt); + tickMouse(); + tickGamepad(); player.tickMove(dt); update(); return dt; diff --git a/game/mainwindow.h b/game/mainwindow.h index 833e58894..cc503bace 100644 --- a/game/mainwindow.h +++ b/game/mainwindow.h @@ -89,7 +89,8 @@ class MainWindow : public Tempest::Window { void setFullscreen(bool fs); void processMouse(Tempest::MouseEvent& event, bool enable); - void tickMouse(uint64_t dt); + void tickMouse(); + void tickGamepad(); void onSettings(); void setupUi(); diff --git a/game/utils/crashlog.cpp b/game/utils/crashlog.cpp index 4373f8ce0..f667931d6 100644 --- a/game/utils/crashlog.cpp +++ b/game/utils/crashlog.cpp @@ -14,7 +14,7 @@ #include #endif -#if defined(__LINUX__) || defined(__APPLE__) +#if (defined(__LINUX__) || defined(__APPLE__)) && !defined(__ANDROID__) #include // backtrace #include // dladdr #include // __cxa_demangle @@ -167,7 +167,7 @@ void CrashLog::tracebackStd(std::ostream &out) { } void CrashLog::tracebackLinux(std::ostream &out) { -#if defined(__LINUX__) || defined(__APPLE__) +#if (defined(__LINUX__) || defined(__APPLE__)) && !defined(__ANDROID__) // inspired by https://gist.github.com/fmela/591333/36faca4c2f68f7483cd0d3a357e8a8dd5f807edf (BSD) void *callstack[64] = {}; char **symbols = nullptr; diff --git a/game/utils/installdetect.cpp b/game/utils/installdetect.cpp index 9c7f3de39..b15be3b0d 100644 --- a/game/utils/installdetect.cpp +++ b/game/utils/installdetect.cpp @@ -1,6 +1,7 @@ #include "installdetect.h" #include +#include #ifdef __WINDOWS__ #include "windows.h" @@ -8,7 +9,11 @@ #include "shlwapi.h" #endif -#include +#ifdef __ANDROID__ +#include +extern "C" struct android_app* tempest_android_get_app(); +#endif + #include "utils/fileutil.h" InstallDetect::InstallDetect() { @@ -16,7 +21,7 @@ InstallDetect::InstallDetect() { pfiles = programFiles(false); pfilesX86 = programFiles(true); #endif -#if defined(__OSX__) || defined(__IOS__) +#if defined(__OSX__) || defined(__IOS__) || defined(__ANDROID__) appDir = applicationSupportDirectory(); #endif } @@ -27,6 +32,14 @@ std::u16string InstallDetect::detectG2() { if(ret.empty()) ret = detectG2(pfilesX86); return ret; +#elif defined(__ANDROID__) + // Check external storage + if(FileUtil::exists(u"/storage/emulated/0/Gothic2/")) + return u"/storage/emulated/0/Gothic2/"; + // First check app's private data directory + if(FileUtil::exists(appDir)) + return appDir; + return u""; #elif defined(__OSX__) || defined(__IOS__) if(FileUtil::exists(appDir)) return appDir; @@ -62,3 +75,20 @@ std::u16string InstallDetect::programFiles(bool x86) { return ret; } #endif + +#ifdef __ANDROID__ +std::u16string InstallDetect::applicationSupportDirectory() { + struct android_app* app = tempest_android_get_app(); + if(app == nullptr || app->activity == nullptr) + return u""; + + // Prefer external data path (accessible without root) + const char* path = app->activity->externalDataPath; + if(path == nullptr) + path = app->activity->internalDataPath; + if(path == nullptr) + return u""; + + return Tempest::TextCodec::toUtf16(path); + } +#endif diff --git a/game/utils/installdetect.h b/game/utils/installdetect.h index 140855660..fdf3709ce 100644 --- a/game/utils/installdetect.h +++ b/game/utils/installdetect.h @@ -8,7 +8,7 @@ class InstallDetect final { InstallDetect(); std::u16string detectG2(); -#if defined(__OSX__) || defined(__IOS__) +#if defined(__OSX__) || defined(__IOS__) || defined(__ANDROID__) static std::u16string applicationSupportDirectory(); #endif @@ -20,7 +20,7 @@ class InstallDetect final { std::u16string pfiles, pfilesX86; #endif -#if defined(__OSX__) || defined(__IOS__) +#if defined(__OSX__) || defined(__IOS__) || defined(__ANDROID__) std::u16string appDir; #endif }; diff --git a/lib/Tempest b/lib/Tempest index 4487b10b5..480afd888 160000 --- a/lib/Tempest +++ b/lib/Tempest @@ -1 +1 @@ -Subproject commit 4487b10b5991f7e3cc48978a9b5cf376e2546dae +Subproject commit 480afd888eb4415b2c70e27f1697626feadfddca diff --git a/shader/CMakeLists.txt b/shader/CMakeLists.txt index 4eda6621a..461a2d5eb 100644 --- a/shader/CMakeLists.txt +++ b/shader/CMakeLists.txt @@ -6,18 +6,18 @@ set(HEADER "${PROJECT_BINARY_DIR}/sprv/shader.h") set(CPP "${PROJECT_BINARY_DIR}/sprv/shader.cpp") file(GLOB_RECURSE SOURCES - "${CMAKE_SOURCE_DIR}/shader/*.vert" - "${CMAKE_SOURCE_DIR}/shader/*.tesc" - "${CMAKE_SOURCE_DIR}/shader/*.tese" - "${CMAKE_SOURCE_DIR}/shader/*.geom" - "${CMAKE_SOURCE_DIR}/shader/*.frag" - "${CMAKE_SOURCE_DIR}/shader/*.glsl" - "${CMAKE_SOURCE_DIR}/shader/*.task" - "${CMAKE_SOURCE_DIR}/shader/*.mesh" - "${CMAKE_SOURCE_DIR}/shader/*.comp") + "${CMAKE_CURRENT_LIST_DIR}/*.vert" + "${CMAKE_CURRENT_LIST_DIR}/*.tesc" + "${CMAKE_CURRENT_LIST_DIR}/*.tese" + "${CMAKE_CURRENT_LIST_DIR}/*.geom" + "${CMAKE_CURRENT_LIST_DIR}/*.frag" + "${CMAKE_CURRENT_LIST_DIR}/*.glsl" + "${CMAKE_CURRENT_LIST_DIR}/*.task" + "${CMAKE_CURRENT_LIST_DIR}/*.mesh" + "${CMAKE_CURRENT_LIST_DIR}/*.comp") file(GLOB_RECURSE GLSL_SOURCES - "${CMAKE_SOURCE_DIR}/shader/*.glsl") + "${CMAKE_CURRENT_LIST_DIR}/*.glsl") # GLSL to SPIRV compiler find_program(GLSLANGVALIDATOR glslangValidator "/opt/homebrew/bin") @@ -27,7 +27,7 @@ endif() function(add_shader OUTPUT SOURCE) set(OUTPUT_FILE "${PROJECT_BINARY_DIR}/sprv/${OUTPUT}.sprv") - set(SOURCE_FILE "${CMAKE_SOURCE_DIR}/shader/${SOURCE}") + set(SOURCE_FILE "${CMAKE_CURRENT_LIST_DIR}/${SOURCE}") set(OPENGOTHIC_SHADERS ${OPENGOTHIC_SHADERS} ${SOURCE_FILE} PARENT_SCOPE) set(OPENGOTHIC_SHADERS_SPRV ${OPENGOTHIC_SHADERS_SPRV} ${OUTPUT_FILE} PARENT_SCOPE) @@ -67,7 +67,7 @@ function(add_shader OUTPUT SOURCE) OUTPUT ${OUTPUT_FILE} DEPENDS ${SOURCE_FILE} ${GLSL_SOURCES} COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/sprv/" - COMMAND ${GLSLANGVALIDATOR} -V ${VARS} -I"${CMAKE_SOURCE_DIR}/shader" "${SOURCE_FILE}" -o ${OUTPUT_FILE} + COMMAND ${GLSLANGVALIDATOR} -V ${VARS} -I"${CMAKE_CURRENT_LIST_DIR}" "${SOURCE_FILE}" -o ${OUTPUT_FILE} ) endfunction(add_shader) @@ -373,13 +373,13 @@ add_custom_command( OUTPUT ${HEADER} ${CPP} DEPENDS ${OPENGOTHIC_SHADERS_SPRV} COMMAND ${CMAKE_COMMAND} -E make_directory "${PROJECT_BINARY_DIR}/sprv/" - COMMAND ${CMAKE_COMMAND} -P "${CMAKE_SOURCE_DIR}/shader/link_shaders.cmake" + COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_LIST_DIR}/link_shaders.cmake" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" ) add_custom_target(shader DEPENDS ${HEADER} ${CPP} - SOURCES ${SOURCES} "${CMAKE_SOURCE_DIR}/shader/link_shaders.cmake") + SOURCES ${SOURCES} "${CMAKE_CURRENT_LIST_DIR}/link_shaders.cmake") add_library(${PROJECT_NAME} STATIC ${HEADER} ${CPP}) add_dependencies(${PROJECT_NAME} shader)