diff --git a/build-android/CMakeLists.txt b/build-android/CMakeLists.txt new file mode 100644 index 0000000..f96ec53 --- /dev/null +++ b/build-android/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.4.1) + +string(TOLOWER ${CMAKE_BUILD_TYPE} ANDROID_BUILD_DIR) +set(DISTRIBUTION_DIR ${CMAKE_SOURCE_DIR}/android/distribution/android/SDL2/intermediates/ndkBuild) +set(SOURCE_FILES src/audio.c src/colours.c src/datatypes.c src/environment.c src/main.c src/vect.c src/basic-lib.c src/controlscheme.c src/draw.c src/game.c src/physics.c) +set(SDL_LOCATION ${CMAKE_SOURCE_DIR}/external/SDL2) + +add_library( SDL2 SHARED IMPORTED ) +add_library( SDL2_mixer SHARED IMPORTED ) +add_library( SDL2_ttf SHARED IMPORTED ) +set_target_properties(SDL2 PROPERTIES IMPORTED_LOCATION +${DISTRIBUTION_DIR}/${ANDROID_BUILD_DIR}/obj/local/${ANDROID_ABI}/libSDL2.so) +set_target_properties(SDL2_mixer PROPERTIES IMPORTED_LOCATION +${DISTRIBUTION_DIR}/${ANDROID_BUILD_DIR}/obj/local/${ANDROID_ABI}/libSDL2_mixer.so) +set_target_properties(SDL2_ttf PROPERTIES IMPORTED_LOCATION +${DISTRIBUTION_DIR}/${ANDROID_BUILD_DIR}/obj/local/${ANDROID_ABI}/libSDL2_ttf.so) + +include_directories(${SDL_LOCATION}/SDL2/SDL2/incl2) +include_directories(${SDL_LOCATION}/SDL2/include) +include_directories(${SDL_LOCATION}/SDL2_mixer) +include_directories(${SDL_LOCATION}/SDL2_ttf) + +add_library( main SHARED ${SDL_LOCATION}/SDL2/src/main/android/SDL_android_main.c ${SOURCE_FILES} ) + +target_link_libraries( main SDL2 SDL2_mixer SDL2_ttf) diff --git a/build-android/LICENSE b/build-android/LICENSE new file mode 100644 index 0000000..03cbdbe --- /dev/null +++ b/build-android/LICENSE @@ -0,0 +1,20 @@ + +hello-sdl2-android +Copyright (C) 2017 Paul Vallet + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/build-android/README.md b/build-android/README.md new file mode 100644 index 0000000..8a1c708 --- /dev/null +++ b/build-android/README.md @@ -0,0 +1,56 @@ +# hello-sdl2-android + +## Synopsis + +This project is an update of the hello world project here https://github.com/stephen47/android-sdl2-gradle-template, as I couldn't find a good way to integrate a CMake built C++ project to an android application. +It features a sample hello world using SDL and SDL_image. + +## Requirements +- JDK and JRE 8 +- Android SDK and NDK (with Android Build-tools 28.0.3 and Android Platform API 28, though these are configurable) +- ANDROID_HOME and ANDROID_NDK_HOME environment variables set (I did this in /etc/environment) + +## Compiling the sample program (command line) + +You can download the dependencies (SDL2 and SDL2_image), compile your program and then install it on a connected device with the following commands: +``` +./get_dependencies +cd android +./gradlew assembleDebug +./gradlew installDebug +``` + +## Compiling the sample program (Android Studio 3.2.1) + +First download the dependencies (SDL2 and SDL2_image) as above or manually like below. Then open the ./android folder as an existing project in Android Studio. + +## Downloading dependencies manually + +Download the latest source release from SDL and SDL_image websites: + +https://www.libsdl.org/download-2.0.php +https://www.libsdl.org/projects/SDL_image/ + +Unzip it, put the SDL2-x.x.x and SDL2_image-x.x.x folders in `external/SDL2` and rename them to SDL2 and SDL2_image so your project folder looks like this: +``` ++ android ++ external +| + SDL2 +| | + Android.mk +| | | SDL2 +| | | SDL2_image +``` + +There, it's done! + +## Thanks + +Most of the code here is inspired by these repositories: + +https://github.com/stephen47/android-sdl2-gradle-template (Android + gradle) + +https://github.com/suikki/simpleSDL/ (Android + CMake) + +The example libSDL2 program which draws the square on screen was found at https://stackoverflow.com/questions/21890627/drawing-a-rectangle-with-sdl2/21903973#21903973. + +The Google NDK example projects were very helpful: https://github.com/googlesamples/android-ndk diff --git a/build-android/android/.gitignore b/build-android/android/.gitignore new file mode 100644 index 0000000..b16bce7 --- /dev/null +++ b/build-android/android/.gitignore @@ -0,0 +1,3 @@ +distribution/ +.gradle/ +.idea/ diff --git a/build-android/android/SDL2/.gitignore b/build-android/android/SDL2/.gitignore new file mode 100644 index 0000000..8375cf7 --- /dev/null +++ b/build-android/android/SDL2/.gitignore @@ -0,0 +1 @@ +.externalNativeBuild/ diff --git a/build-android/android/SDL2/build.gradle b/build-android/android/SDL2/build.gradle new file mode 100644 index 0000000..d54e243 --- /dev/null +++ b/build-android/android/SDL2/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + ndk { + abiFilters "armeabi-v7a" + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + ndkBuild { + path '../../external/SDL2/Android.mk' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:28.0.0' + testCompile 'junit:junit:4.12' +} diff --git a/build-android/android/SDL2/proguard-rules.pro b/build-android/android/SDL2/proguard-rules.pro new file mode 100644 index 0000000..c662747 --- /dev/null +++ b/build-android/android/SDL2/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/luap/Logiciels/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/build-android/android/app/.gitignore b/build-android/android/app/.gitignore new file mode 100644 index 0000000..8375cf7 --- /dev/null +++ b/build-android/android/app/.gitignore @@ -0,0 +1 @@ +.externalNativeBuild/ diff --git a/build-android/android/app/build.gradle b/build-android/android/app/build.gradle new file mode 100644 index 0000000..217465d --- /dev/null +++ b/build-android/android/app/build.gradle @@ -0,0 +1,45 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + buildToolsVersion "28.0.3" + defaultConfig { + applicationId "pvallet.com.github.hello_sdl2" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "-std=c++11 -frtti -fexceptions" + } + } + ndk { + abiFilters "armeabi-v7a" + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "../../CMakeLists.txt" + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:28.0.0' + // Uncomment this line to build SDL2 + // Uncomment a line in ../gradle.settings as well + compile project(':SDL2') + testCompile 'junit:junit:4.12' +} diff --git a/build-android/android/app/proguard-rules.pro b/build-android/android/app/proguard-rules.pro new file mode 100644 index 0000000..c662747 --- /dev/null +++ b/build-android/android/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /home/luap/Logiciels/android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/build-android/android/build.gradle b/build-android/android/build.gradle new file mode 100644 index 0000000..b9278c2 --- /dev/null +++ b/build-android/android/build.gradle @@ -0,0 +1,31 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +// Instead of having a build dir inside android app dir use the same project top level build dir to reduce clutter +buildDir = file("distribution/${rootProject.name}/${project.name}") +subprojects { + buildDir = file("../distribution/${rootProject.name}/${project.name}") +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/build-android/android/gradle.properties b/build-android/android/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/build-android/android/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/build-android/android/gradle/wrapper/gradle-wrapper.jar b/build-android/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/build-android/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/build-android/android/gradle/wrapper/gradle-wrapper.properties b/build-android/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..caeb3ee --- /dev/null +++ b/build-android/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jul 05 15:54:11 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip diff --git a/build-android/android/gradlew b/build-android/android/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/build-android/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/build-android/android/gradlew.bat b/build-android/android/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/build-android/android/gradlew.bat @@ -0,0 +1,90 @@ +@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 + +@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= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/build-android/android/settings.gradle b/build-android/android/settings.gradle new file mode 100644 index 0000000..031d202 --- /dev/null +++ b/build-android/android/settings.gradle @@ -0,0 +1,4 @@ +include ':app' +// Uncomment this line to build SDL2 +// Uncomment the dependency line in app/build.gradle as well +include ':SDL2' diff --git a/build-android/get_dependencies.sh b/build-android/get_dependencies.sh new file mode 100755 index 0000000..5b73224 --- /dev/null +++ b/build-android/get_dependencies.sh @@ -0,0 +1,17 @@ +#!/bin/bash -x + +pushd external/SDL2 + +wget https://www.libsdl.org/release/SDL2-2.0.8.zip +unzip -q SDL2-2.0.8.zip +mv SDL2-2.0.8 SDL2 +rm SDL2-2.0.8.zip + +wget https://www.libsdl.org/projects/SDL_image/release/SDL2_image-2.0.3.zip +unzip -q SDL2_image-2.0.3.zip +mv SDL2_image-2.0.3 SDL2_image +rm SDL2_image-2.0.3.zip + +popd +# No need to copy SDLActivity.java et al, repo contains those from 2.0.8 +# cp external/SDL2/SDL2/android-project/app/src/main/java/org/libsdl/app/*.java android/app/src/main/java/org/libsdl/app/ diff --git a/build-android/res/TerminusTTF.ttf b/build-android/res/TerminusTTF.ttf new file mode 100644 index 0000000..d302264 Binary files /dev/null and b/build-android/res/TerminusTTF.ttf differ diff --git a/build-android/res/hello.png b/build-android/res/hello.png new file mode 100644 index 0000000..9150b3b Binary files /dev/null and b/build-android/res/hello.png differ diff --git a/build-android/src/audio.c b/build-android/src/audio.c new file mode 100644 index 0000000..32949b3 --- /dev/null +++ b/build-android/src/audio.c @@ -0,0 +1,226 @@ +#include "audio.h" +#include +#include +#include + +void generate_audio(void); +struct game_sounds game_sounds; + +Uint8 big_hum(unsigned int t); +Uint8 func0(unsigned int t); + +int mute = false; + + void fill_audio(void *func, Uint8 *stream, int len) { + Uint8 (*wavegen)(unsigned int) = func; + + unsigned int t = 1; + for(int i = 0; i < len; i++,t++) { + stream[i] = wavegen(t); + } +} + +void callbackfn3(void *unused, Uint8 *stream, int len) { + static int t = 1; + for(int i = 0; i < len; i++,t++) { + stream[i] = big_hum(t); + stream[i] = 0; + } +} + +void start_audio(void) { + + //audio_format.callback = callbackfn3; + /* just for playing background music + if (SDL_OpenAudio(&audio_format, NULL)) { + fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); + exit(1); + } + */ + //SDL_PauseAudio(0); + if (Mix_OpenAudio(G_AUDIO_SFREQ,AUDIO_U8,1,G_AUDIO_BSIZE) < 0) { + fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); + exit(1); + } + + if (Mix_AllocateChannels(20) < 0) { + fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); + } + + generate_audio(); + +} + +void +play_game_sound(Mix_Chunk *chunk, int len, int channel) +{ + if (mute || Mix_Playing(channel)) { + return; + } + + if(Mix_PlayChannelTimed(channel, chunk, -1, len) < 0) + fprintf(stderr, "Unable to play audio: %s\n", SDL_GetError()); + //Mix_FadeInChannelTimed(channel, chunk, -1, 1, len); +} + +Uint8 +pitchshift_hit(unsigned int t, void *arg) +{ + double *hitt = arg; + double hit = *hitt; + hit = 6 - hit; + int lower = 10 + 2 * (hit > 6 ? 6 : hit); + int higher = 1 + (hit > 6 ? 6 : hit); +// printf("mag %f higher %d lower %d\n", hit, higher, lower); + + const double R=8000; // sample rate (samples per second) + const double C=125; // frequency of middle-C (hertz) + const double F=R/256; // bytebeat frequency of 1*t due to 8-bit truncation (hertz) + const double V=127; // a volume constant + double volume = (((t / 3000) > 2) ? 2 : t / 3000); + + return ((sin(t*2*M_PI/R*(125 + hit))+1)*10 + (sin(t*2*M_PI/R*(250 + hit))+1)*(50 + hit) + (sin(t*2*M_PI/R*(900 + hit))+1)*100 + (sin(t*2*M_PI/R*(150+ hit))+1)*5) ; + + return ((t < 3000) * ((t % higher) * (3000 - t % 3000) * 0.00351)); + //+ ((t < 3500) * ((3500 - t % 3500)) * 0.0002 * (t % lower)); +} + +void +synthplay_now(Uint8 (*bbgen)(unsigned int t, void *), void *arg, int len) +{ + static Mix_Chunk *new_chunk = NULL; + + if (Mix_Playing(1)) { + return; + } + + if (new_chunk) + Mix_FreeChunk(new_chunk); + + int generate_len = len * (512); + + Uint8 raw_data[generate_len]; + + unsigned int t = 1; + for(int i = 0; i < generate_len; i++,t++) { + raw_data[i] = bbgen(t, arg); + } + + new_chunk = Mix_QuickLoad_RAW(raw_data, generate_len); + Mix_PlayChannelTimed(1, new_chunk, -1, len); +} + + + +void callbackfn2(void *unused, Uint8 *stream, int len) { + static int t = 1; + int i; + int yeet = rand(); + for(i=0; i < len; i++,t++) { + stream[i] = func0(t); + } +} + + +void callbackfn(void *unused, Uint8 *stream, int len) +{ + static int t = 0; + int i; + int yeet = rand(); + for( i=0; i < len; i++,t++) { + /* + stream[i] = (t*5&(t>>7))|(t*3&(t>>8)); + */ + //stream[i] = (( t >> 10 ) & 42) * t; + stream[i] = (t & yeet) % 73; + } +} + +/* Waveform Generators */ + +Uint8 big_hum(unsigned int t) { + return ( (sin(t*2*3.14159265/8000*200)+1) * 20) + ( (sin(t*2*3.14159265/8000*110)+1) * 20) + ( (sin(t*2*3.14159265/8000*230)+1) * 20) + ( (sin((t % 80)*2*3.14159265/8000 * 6)+1) * 12); +} + +unsigned int sine_wave_sound(unsigned int t) { + const double R=8000; // sample rate (samples per second) + const double C=261.625565; // frequency of middle-C (hertz) + const double F=R/256; // bytebeat frequency of 1*t due to 8-bit truncation (hertz) + const double V=127; // a volume constant + return (sin(t*2*M_PI/R*C)+1)*V; +} + +Uint8 winch_sound(unsigned t) { + return (((t % 80) | (t % 36) | (6 * (t % 6))) + 20) * 1.6; + return ((t % 80) | (t % 36) | (6 * (t % 6))); +} + +Uint8 beat_2(unsigned t) { + return ((((t % 250) / 2)) | (t % 129)) % 110; +} + +Uint8 func0(unsigned t) { + int note = 18; + if (!(t % 1240)) { + note = (1 + rand()) % 18 + 1; + } + + fflush(stdout); + return (t % note); +} + +Uint8 func1(unsigned int t) { + int note = 10; + if (!(t % 8000)) { + note = (1 + rand()) % 10 + 5; + } + return ((t & 110) % 73 | (t % 1711)); +} + +Uint8 collision_test(unsigned int t) { + // try not to loop + return ((t < 3000) * ((t % 5) * (3000 - t % 3000) * 0.00351)) + + ((t < 3500) * ((3500 - t % 3500)) * 0.0002 * (t % 14)); + return ((t < 3000) * ((t % 5) * (3000 - t % 3000) * 0.00351)) + + ((4000 - t % 4000) * 0.0003 * (t % 128)) + + ((t < 3500) * ((3500 - t % 3500)) * 0.0002 * (t % 64)); // has a big low sound + return (t % 4) * (4000 - t % 4000) * 0.01; +} + + +Uint8 boop_noise(unsigned int t) { + return ((t >> 10) & 42) * t; +} + +Uint8 rope_attach(unsigned int t) { + return (((2000 - (t % 2000)))/40 * (t % 20) | (t % 44) + 20) * 1.6; +} + +void generate_audio(void) { + const int sfx_len_ms = 1000; + unsigned int sfx_len = (G_AUDIO_SFREQ/1000) * G_AUDIO_BSIZE * sfx_len_ms; + sfx_len = G_AUDIO_BSIZE; + + Uint8 *raw_data = malloc(sfx_len); + fill_audio(winch_sound, raw_data, sfx_len); + game_sounds.rope_pull = Mix_QuickLoad_RAW(raw_data, sfx_len); + Mix_Volume(BC_ROPE_PULL, MIX_MAX_VOLUME * 0.3); + + Uint8 *raw_data2 = malloc(sfx_len); + fill_audio(collision_test, raw_data2, sfx_len); + game_sounds.collision = Mix_QuickLoad_RAW(raw_data2, sfx_len); + Mix_Volume(BC_COLLISION, MIX_MAX_VOLUME * 1.2); + + Uint8 *raw_data3 = malloc(sfx_len); + fill_audio(big_hum, raw_data3, sfx_len); + game_sounds.background = Mix_QuickLoad_RAW(raw_data3, sfx_len); + Mix_Volume(BC_MUSIC, MIX_MAX_VOLUME * 0.1); + + Uint8 *raw_data4 = malloc(sfx_len); + fill_audio(rope_attach, raw_data4, sfx_len); + game_sounds.rope_attach = Mix_QuickLoad_RAW(raw_data4, sfx_len); + Mix_Volume(BC_ROPE_ATTACH, MIX_MAX_VOLUME * 0.1); + +} + + diff --git a/build-android/src/audio.h b/build-android/src/audio.h new file mode 100644 index 0000000..72d96cf --- /dev/null +++ b/build-android/src/audio.h @@ -0,0 +1,37 @@ + +#ifndef AUDIO_H +#define AUDIO_H + +#include +#include "types.h" +#include +#include + +#define G_AUDIO_SFREQ 8000 +#define G_AUDIO_BSIZE 512 + +struct game_sounds { + unsigned int t; + Mix_Chunk *collision; + Mix_Chunk *rope_attach; + Mix_Chunk *rope_pull; + Mix_Chunk *background; + /* Looping samples (need backbuffer to fill while playing ) */ + Mix_Chunk *menu; +}; + +extern struct game_sounds game_sounds; + +void play_game_sound(Mix_Chunk *chunk, int len, int channel); +void start_audio(void); + +Uint8 pitchshift_hit(unsigned int t, void *arg); +void synthplay_now(Uint8 (*bbgen)(unsigned int t, void *), void *arg, int len); + +enum audio_channel_names { + BC_COLLISION = 1, + BC_ROPE_PULL = 2, + BC_ROPE_ATTACH = 3, + BC_MUSIC = 4, +}; +#endif diff --git a/build-android/src/basic-lib.c b/build-android/src/basic-lib.c new file mode 100644 index 0000000..e46df59 --- /dev/null +++ b/build-android/src/basic-lib.c @@ -0,0 +1,97 @@ +#include +#include +#include + +// #define LOGGING 1 +// #define log_var(string, content) if (LOGGING) {time_t current_time; time(¤t_time); printf("%s",asctime(localtime(¤t_time))); printf(string, content);} +// #define log_str(string) if (LOGGING) {time_t current_time; time(¤t_time); puts(asctime(localtime(¤t_time))); puts(string);} + +void enumerate_string(char test_array[]) { + char *ptr; + ptr = &(test_array[0]); + for (int i=0; i<5; i++) { + printf("%c", *(ptr + i)); + } +} + +int copy_str(char * out_string, char * string) { + /* A drop in replacement for strcpy() from */ + int len = 0; + while (*(string + len) != '\0') { + *(out_string + len) = *(string + len); + len++; + } + *(out_string + len) = '\0'; + return (1); +} + +int get_str_len(char * string) { + int len = 0; + while (*(string + len) != '\0') { + len++; + } + return(len); +} + +void replace_char(char * array, char * charac) { + int length; + length = sizeof(*array)/sizeof(char); + char combined[length]; + + int i = 0; + int counter = 0; + + while (*(array + i) != '\0') { + if (*(array + i) != *charac) { + *(array + counter) = *(array + i); + counter++; + } + i++; + } + *(array + counter + 1) = '\0'; +} + +void join_strings(char * return_val, char * str_1, char * str_2) { + + int len1 = get_str_len(str_1); + int len2 = get_str_len(str_2); + + int length = len1 + len2; + char combined[length+1]; + int i = 0; + int j = 0; + + while (*(str_1 + i) != '\0') { + combined[i] = *(str_1 + i); + i++; + } + + while (*(str_2 + j) != '\0') { + combined[len1+j] = *(str_2 + j); + j++; + } + copy_str(return_val, combined); +} + +void get_time(void) { + char time_str[100]; + time_t current_time; + time(¤t_time); + copy_str(time_str, asctime(localtime(¤t_time))); + + strcat(time_str, "hello world"); + //replace_char(&(time_str[0]), "\n"); + printf("%s", time_str); + + char string[20]; + copy_str(string, "NONEWLINEhello"); + + + replace_char(&(time_str[0]), "\n"); + char newstr[120]; + join_strings(&(newstr[0]), &(time_str[0]), &(string[0])); + replace_char(&(newstr[0]), "\n"); + + printf("%s", newstr); +} + diff --git a/build-android/src/colours.c b/build-android/src/colours.c new file mode 100644 index 0000000..9a88792 --- /dev/null +++ b/build-android/src/colours.c @@ -0,0 +1,238 @@ +#include "colours.h" + +struct colour get_random_color(unsigned int seed) { + srand(seed); + int red = rand() % 255; + int blue = rand() % 255; + int green = rand() % 255; + + struct colour col; + col.r = red; + col.g = green; + col.b = blue; + return col; +} + +double m_min(double arr[], int len) { + double largest = arr[0]; + for (int i = 0; i < len; i ++) { + if (arr[i] < largest) { + largest = arr[i]; + } + } + return largest; +} + +double m_max(double arr[], int len) { + double largest = arr[0]; + for (int i = 0; i < len; i ++) { + if (arr[i] > largest) { + largest = arr[i]; + } + } + return largest; +} + +bool m_equal(double a, double b) { + return (a - b) * (a - b) < 0.00001; +} + +// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB +// h = [0,360], s = [0,1], v = [0,1] +// if s == 0, then h = -1 (undefined) +struct colour get_hs_l_v(struct colour c, enum colour_space sp) { + struct colour ret; + memset(&ret, 0, sizeof(struct colour)); + double r = (double)c.r / 255; + double g = (double)c.g / 255; + double b = (double)c.b / 255; + + double arr[] = {r, g, b}; + double max = m_max(arr, 3); + double min = m_min(arr, 3); + + ret.v = max; + + double chroma = max - min; + + double lum = (max + min) / 2; + ret.l = lum; + + if (m_equal(chroma, 0)) { + ret.h = 0; + } else if (m_equal(ret.v,r)) { + ret.h = (g - b) / chroma; + } else if (m_equal(max,g)) { + ret.h = 2 + (b - r) / chroma; + } else if (m_equal(max,b)) { + ret.h = 4 + (r - g) / chroma; + } else { + printf("NOPE BAD ERROR\n"); + } + + ret.h *= 60; + ret.h = fmod(ret.h, 360); + if (ret.h < 0) { + ret.h += 360; + } + + if (sp == CS_HSV) { + if (m_equal(max,0)) { + ret.s = 0; + } else { + ret.s = chroma / max; + } + } else { + double arr[] = {ret.l, 1 - ret.l}; + if (m_equal(0, lum) || m_equal(1, lum)) { + ret.s = 0; + } else { + ret.s = (ret.v - ret.l) / m_min(arr, 2); + } + } + ret.sp = CS_HSL; + return ret; +} + +struct colour get_hsl(struct colour c) { + return get_hs_l_v(c, CS_HSL); +} + +struct colour get_hsv(struct colour c) { + return get_hs_l_v(c, CS_HSV); +} + +double magic_hsv_function(int n, struct colour c) { + double res = 0; + double k = fmod(n + (c.h / 60), 6); + double arr[] = {k, 4 - k, 1}; + double k_b = m_min(arr, 3); + double k_c = 0 > k_b ? 0 : k_b; + res = c.v - c.v * c.s * k_c; + return res; +} + +struct colour get_rgb_from_hsv(struct colour c) { + + double r = magic_hsv_function(5, c); + double g = magic_hsv_function(3, c); + double b = magic_hsv_function(1, c); + + struct colour res; + res.r = round(r * 255); + res.g = round(g * 255); + res.b = round(b * 255); + return res; + +} + +double magic_hsl_function(int n, struct colour c) { + double arr[] = {c.l, 1-c.l}; + double a = c.s * ( c.l < (1-c.l) ? c.l : 1 - c.l ); + double k = fmod(n + c.h / 30, 12); + + double b[] = {k - 3, 9 -k, 1}; + double d[] = {-1, m_min(b, 3)}; + return c.l - a * (d[0] > d[1] ? d[0] : d[1]); +} + +struct colour get_rgb_from_hsl(struct colour c) { + double r = magic_hsl_function(0, c); + double g = magic_hsl_function(8, c); + double b = magic_hsl_function(4, c); + + struct colour res; + res.r = round(r * 255); + res.g = round(g * 255); + res.b = round(b * 255); + return res; +} + +struct colour get_rgb(struct colour c) { + if (c.sp == CS_HSL) { + return get_rgb_from_hsl(c); + } + if (c.sp == CS_HSV) { + return get_rgb_from_hsv(c); + } + + struct colour d; + memset(&d, 0, sizeof(struct colour)); + return d; +} + +int compare_perceived_lum(const void* c1, const void* c2) { + struct colour rgb_c1 = *(struct colour*)c1; + struct colour rgb_c2 = *(struct colour*)c2; + + double lum1 = 0.299*rgb_c1.r + 0.587*rgb_c1.g + 0.114*rgb_c1.b; + double lum2 = 0.299*rgb_c2.r + 0.587*rgb_c2.g + 0.114*rgb_c2.b; + return lum1 - lum2; +} + +struct colour *get_adjacent(struct colour base, int deg, int num) { + struct colour col = get_hsl(base); + num += 1; + + struct colour* colours = calloc(sizeof(struct colour), num); + + for (int i = 0; i < num; i++) { + int m = (i % 2 == 0) ? -i : i; + + colours[i] = col; + colours[i].h += m * deg; + colours[i].h += fmod(colours[i].h, 360); + } + + struct colour *ret_colours = calloc(num, sizeof(struct colour) * num); + + for (int i = 0; i < num; i++) { + ret_colours[i] = get_rgb(colours[i]); + } + + // sort from dark to bright + qsort(ret_colours, num, sizeof(struct colour), compare_perceived_lum); + + free(colours); + return ret_colours; +} + +void print_colour(struct colour c) { + char *colour = calloc(20, sizeof(char)); + sprintf(colour, "#%02x%02x%02x", c.r, c.g, c.b); + + printf("\x1b[38;2;%d;%d;%dm%s\x1b[0m\n", c.r, c.g, c.b, colour); + free(colour); +} + +void test(int seed) { + struct colour c = get_random_color(seed); + //print_colour(c); + struct colour hsl = get_hsl(c); + struct colour hsv = get_hsv(c); + /*printf("RGB: %d %d %d\n",c.r,c.g,c.b);*/ + /*printf("HSL: %f %f %f\n",hsl.h, hsl.s, hsl.l);*/ + /*printf("HSV: %f %f %f\n",hsv.h, hsv.s, hsv.v);*/ + + struct colour *adj = get_adjacent(c, 5, 4); + for (int i = 0; i < 5; i++) { + print_colour(adj[i]); + } + + free(adj); + printf("\n"); +} + +void test_print_wheel() { + struct colour c; + c.s = 0.999; + c.v = 0.99; + c.l = 0.5; + c.sp = CS_HSV; + for(int i = 0; i < 360; i+=5) { + c.h = (double)i; + struct colour rgb = get_rgb(c); + print_colour(rgb); + + } +} diff --git a/build-android/src/colours.h b/build-android/src/colours.h new file mode 100644 index 0000000..849cca2 --- /dev/null +++ b/build-android/src/colours.h @@ -0,0 +1,43 @@ + +#ifndef COSMOPOLITAN +#include +#include +#include +#include +#include +#endif + +#ifndef H_COLOURS +#define H_COLOURS + +enum colour_space { + CS_INVALID = 0, + CS_RGB = 1, // default to RGB + CS_HSV = 2, + CS_HSL = 3, +}; + +struct colour { + double h; + double s; + double v; + double l; + int r; + int g; + int b; + enum colour_space sp; +}; + +struct colour get_random_color(unsigned int seed); + +// doesn't support hsl-hsv or converse, conversion +struct colour get_hsl(struct colour c); +struct colour get_hsv(struct colour c); + +struct colour get_rgb(struct colour c); + +struct colour *get_adjacent(struct colour base, int deg, int num); + +void print_colour(struct colour c); + +#endif diff --git a/build-android/src/controlscheme.c b/build-android/src/controlscheme.c new file mode 100644 index 0000000..a24365d --- /dev/null +++ b/build-android/src/controlscheme.c @@ -0,0 +1,106 @@ + +#include "controlscheme.h" +#include + +#ifdef LOAD_INPUTS +#include +#endif + +struct InputMap input_map; + +#define INPUT_MAP_ID 83921864 +#define INPUT_MAP_VERSION 0 +#define CONTROLS_FNAME "controls.bin" + +struct InputMap_Ser { + unsigned int id; + unsigned int ver; + struct InputMap input_map; +}; + +int load_controls(void); + +void get_input_map(void) { + +#ifdef LOAD_INPUTS + if (load_controls()) { + return; + } +#endif + + input_map.player_down = SDL_SCANCODE_S; + input_map.player_up = SDL_SCANCODE_W; + input_map.player_right = SDL_SCANCODE_D; + input_map.player_left = SDL_SCANCODE_A; + input_map.player_rope = SDL_SCANCODE_E; + input_map.player_pull_rope = SDL_SCANCODE_Q; + input_map.mute = SDL_SCANCODE_M; + input_map.pause = SDL_SCANCODE_ESCAPE; + input_map.quit = SDL_SCANCODE_Q; + input_map.goto_level = SDL_SCANCODE_G; + input_map.mouse_attach_rope_pull = SDL_BUTTON_LEFT; + input_map.mouse_attach_rope = SDL_BUTTON_RIGHT; +} + +#ifdef LOAD_INPUTS +struct Buffer { + size_t len; + char *data; +}; + +struct Buffer read_whole_file(char const *file_name) { + struct stat sb; + struct Buffer b = {}; + + if (stat (file_name, & sb) != 0) { + return b; + } + + char *buffer = malloc(sb.st_size + 1); + + FILE *f = fopen(file_name, "r"); + size_t bytes_read = fread(buffer, sizeof (unsigned char), sb.st_size, f); + if (bytes_read != sb.st_size) { + fclose(f); + free(buffer); + return b; + } + + fclose(f); + + b.data = buffer; + b.len = bytes_read; + return b; +} + +int load_controls(void) { + struct Buffer b = read_whole_file(CONTROLS_FNAME); + + if (b.len != sizeof (struct InputMap_Ser)) { + return 0; + } + + struct InputMap_Ser *im = (struct InputMap_Ser *)b.data; + + if (im->id != INPUT_MAP_ID) + return 0; + + if (im->ver != INPUT_MAP_VERSION) + return 0; + + memcpy(&input_map, &im->input_map, sizeof(struct InputMap)); + return 1; +} + +void save_controls(void) { + FILE *f = fopen(CONTROLS_FNAME, "w"); + + struct InputMap_Ser im; + im.id = INPUT_MAP_ID; + im.ver = INPUT_MAP_VERSION; + im.input_map = input_map; + + fwrite(&im, sizeof(unsigned char), sizeof(struct InputMap_Ser), f); + fclose(f); +} +#endif diff --git a/build-android/src/controlscheme.h b/build-android/src/controlscheme.h new file mode 100644 index 0000000..78da83c --- /dev/null +++ b/build-android/src/controlscheme.h @@ -0,0 +1,35 @@ +#include + +#ifndef CRTL_H +#define CRTL_H + +enum Game_Interaction { + PLAYER_UP, + PLAYER_DOWN, + PLAYER_RIGHT, + PLAYER_LEFT +}; + + +struct InputMap { + SDL_Scancode player_up; + SDL_Scancode player_down; + SDL_Scancode player_left; + SDL_Scancode player_right; + SDL_Scancode player_rope; + SDL_Scancode player_pull_rope; + SDL_Scancode mute; + SDL_Scancode quit; + SDL_Scancode pause; + SDL_Scancode goto_level; + Uint8 mouse_attach_rope_pull; + Uint8 mouse_attach_rope; +}; + +extern struct InputMap input_map; + +void get_input_map(void); +void save_controls(void); + +#endif + diff --git a/build-android/src/datatypes.c b/build-android/src/datatypes.c new file mode 100644 index 0000000..534ea3a --- /dev/null +++ b/build-android/src/datatypes.c @@ -0,0 +1,159 @@ +#include "datatypes.h" +#include +#include + +ArrayList new_arlst(int num_elems) { + ArrayList new_list; + new_list.free = NULL; + new_list.size = 0; + new_list.capacity = num_elems; + new_list.items = calloc(num_elems, sizeof(void *)); + + return new_list; +} + +ArrayList new_arlst_wfree(int num_elems, void (*nfree)(void *ptr)) { + ArrayList new_list = new_arlst(num_elems); + new_list.free = nfree; + return new_list; +} + +ArrayList new_arlst_using_array(void **array, int length) { + ArrayList l = new_arlst(length); + free(l.items); + l.items = array; + return l; +} + +ArrayList new_arlst_using_array_wfree(void **array, int length, + void (*f)(void *)) { + ArrayList l = new_arlst_using_array(array, length); + l.free = f; + return l; +} + +ArrayList new_arlst_from_array(void *array, size_t elem_size, int length) { + ArrayList l = new_arlst(length); + l.free = free; + + for (int i = 0; i < length; i++) { + void *item = calloc(1, elem_size); + void *start = array + i * elem_size; + memmove(item, start, elem_size); + arlst_add(&l, item); + } + return l; +} + +void *arlst_get(ArrayList *l, int id) { + // out of bounds + if (id < 0 || id >= l->size) { + return 0; + } + + return l->items[id]; +} + +void * arlst_del(ArrayList *l, int id) { + void *item = arlst_get(l, id); + + if (!item) { + return NULL; + } + + l->size--; + // move elements backward + for (int i = id; i < l->size; i++) { + l->items[i] = l->items[i+1]; + } + + + // shrink capacity if neccessary + if (D_SHRINK_THRESHOLD != 0 + || l->size < (100 / D_SHRINK_THRESHOLD) * l->capacity) { + + size_t new_size = l-> size + (l->capacity * (100 / D_SHRINK_THRESHOLD) * + 100 / D_GROW_AMOUNT); + +// l->items = reallocarray(l->items, new_size, sizeof(void *)); + l->items = realloc(l->items, new_size * sizeof(void *)); + } + + return item; +} + +int arlst_add(ArrayList *l, void *item) { + int item_index = l->size; + + // grow if neccessary + if (l->size == l->capacity) { + size_t new_size = l->capacity + (100 / D_GROW_AMOUNT) * l->capacity; + l->capacity = new_size; + //l->items = (void **)reallocarray(l->items, new_size, sizeof(void *)); + l->items = (void **)realloc(l->items, new_size * sizeof(void *)); + } + + l->items[item_index] = item; + l->size++; + + return 0; +} + +void * arlst_pop(ArrayList *l) { + l->size--; + return l->items[l->size]; +} + +int arlst_push(ArrayList *l, void *item) { + return arlst_add(l, item); +} + +int arlst_destroy(ArrayList *l) { + if (l->free != NULL) { + for (int i = 0; i < l->size; i++) { + l->free(l->items[i]); + } + } + free(l->items); + return 0; +} + + +ListFiFo new_fifo(int start_size) { + ListFiFo l; + memset(&l, 0, sizeof(ListFiFo)); + l.inner = new_arlst(start_size); + l.size = &l.inner.size; + return l; +} + +void *fifo_get(ListFiFo *l) { + if (l->fifo_head >= l->inner.size) { + return NULL; + } + + void *value = arlst_get(&l->inner, l->fifo_head); + + if (value != NULL) { + l->fifo_head++; + } + + if (l->fifo_head == l->inner.capacity + || (l->fifo_head == l->inner.size + && l->inner.size >= D_MAX_QUEUE_LEN)) { + // start rewriting from the beginning + l->inner.size = 0; + l->fifo_head = 0; + // memset(l->inner.items, 0, sizeof(void *) * l->inner.capacity); + } + return value; +} + +/* It is up to the caller to free any data stored */ +int fifo_add(ListFiFo *l, void *i) { + if (l->inner.size >= D_MAX_QUEUE_LEN) { + return -1; + } + return arlst_add(&l->inner, i); +} + diff --git a/build-android/src/datatypes.h b/build-android/src/datatypes.h new file mode 100644 index 0000000..efb5ec2 --- /dev/null +++ b/build-android/src/datatypes.h @@ -0,0 +1,61 @@ +#ifndef DTYPE_H +#define DTYPE_H + +#include + +enum { + D_SHRINK_THRESHOLD = 50, // percentage of capacity + // 0 = never shrink + // 100 = always shrink + D_GROW_AMOUNT = 50, // percentage of size + // 0 = 1 = grow by 1 on every add + // 100 = double size whenever space runs out + D_MAX_QUEUE_LEN = 400 // maximum num of items allowed in a fifo +}; + +struct array_list { + size_t size; + size_t capacity; + + void **items; + void (*free)(void *ptr); // free function +}; + +struct list_fifo { + struct array_list inner; + size_t fifo_head; + size_t *size; +}; + +typedef struct array_list ArrayList; +typedef struct list_fifo ListFiFo; + +/* FIFOs */ + +ListFiFo new_fifo(int start_size); +int fifo_add(ListFiFo *l, void *i); +void *fifo_get(ListFiFo *l); + +/* Stacks */ + +int arlst_push(ArrayList *l, void *item); +void * arlst_pop(ArrayList *l); + +/* Array Lists */ + +// constructors +ArrayList new_arlst_from_array(void *array, size_t elem_size, int length); +ArrayList new_arlst_using_array_wfree(void **, int, void (*)(void *)); +ArrayList new_arlst_using_array(void **array, int length); +ArrayList new_arlst_wfree(int num_elems, void (*nfree)(void *ptr)); +ArrayList new_arlst(int num_elems); + +// destructors +int arlst_destroy(ArrayList *l); +void * arlst_del(ArrayList *l, int id); + +// functions +int arlst_add(ArrayList *l, void *item); +void *arlst_get(ArrayList *l, int id); + +#endif diff --git a/build-android/src/debuginfo.h b/build-android/src/debuginfo.h new file mode 100644 index 0000000..5870433 --- /dev/null +++ b/build-android/src/debuginfo.h @@ -0,0 +1,2 @@ + +#define SHOWCOLLISIONBOX 1 diff --git a/build-android/src/draw.c b/build-android/src/draw.c new file mode 100644 index 0000000..64cbdde --- /dev/null +++ b/build-android/src/draw.c @@ -0,0 +1,881 @@ +#include "draw.h" +#include "game.h" +#include "types.h" +#include +#include +#include + +Vect viewport_pos; +int width, height; + +bool high_contrast_mode = 0; + +/* generic rendering functions */ +int MAX_ONSCREEN_OBJECTS = 99; + +int num_onscreen = 0; + +double time_delta = 0; + +void render_texture_at(struct SDL_Renderer * ren, struct SDL_Texture * texture,int x, int y) { + /* draw a texture at x.y */ + + SDL_Rect destination; + destination.x = x; + destination.y = y; + + SDL_QueryTexture(texture, NULL, NULL, &destination.w, &destination.h); + + SDL_RenderCopy(ren, texture, NULL, &destination); +} + +/*SDL_Texture * load_image(SDL_Renderer * ren, char fname[]) {*/ + /*[> Load an image into a texture <]*/ + + /*SDL_Surface * surface = IMG_Load(fname) ;*/ + /*if(!surface) {*/ + /*printf("IMG_Load: %s\n", IMG_GetError());*/ + /*// handle error*/ + /*}*/ + + /*SDL_Texture * png_image = SDL_CreateTextureFromSurface(ren, surface);*/ + /*cleanup(surface, SURFACE);*/ + /*return (png_image);*/ +/*}*/ + + +double sigmoid(double x) { + return exp(x) / (exp(x) + 1); +} + +struct timespec get_now_d() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_nsec >= 1000000000) { + now.tv_nsec -= 1000000000; + } + return now; +} + +Vect in_view(Vect V) { + Vect ret; + ret.x = V.x -viewport_pos.x; + ret.y = V.y -viewport_pos.y; + return ret; +} + +/* Game Specific rendering functions */ +void draw_player(SDL_Renderer * ren, int x, int y, bool red) { + // translate to viewport + + Vect V; + V.x = x; V.y = y; + V = in_view(V); + + /* draw the player as a coloured rect */ + SDL_Rect player_rect; + + player_rect.x = V.x -10; + player_rect.y = V.y -10; + + player_rect.w = 20; + player_rect.h = 20; + + struct colour c = get_scene_watch()->colours.bg; + + SDL_SetRenderDrawColor(ren, c.r, c.g, c.b, 255); + + SDL_RenderDrawRect(ren, &player_rect); + SDL_RenderFillRect(ren, &player_rect); + // draw strings + for (int i = 0; i < player.physics->num_strings; i++) { + if (!player.physics->strings[i].attached) { + continue; + } + Vect B = {x, y}; + B = in_view(B); + Vect E; + E = in_view(player.physics->strings[i].end_point); + SDL_RenderDrawLine(ren, B.x, B.y, E.x, E.y); + } + + + SDL_SetRenderDrawColor(ren, 0,0,0, 255); + +} + +void draw_floor(SDL_Renderer *ren, FloorPoly *poly, bool down) { + SDL_Point left; + SDL_Point right; + left.x = poly->left.x - viewport_pos.x; + + if (left.x > width) { + return; + } + + right.x = poly->right.x - viewport_pos.x; + if (right.x < 0) { + return; + } + + left.y = poly->left.y - viewport_pos.y; + right.y = poly->right.y - viewport_pos.y; + + if (left.y <= 0 && right.y <= 0) { + return; + } + if (left.y >= height && right.y >= height) { + return; + } + + SDL_SetRenderDrawColor(ren, 100,100,100, 255); + + int end = down ? height : 0; + + double m = (double)(right.y - left.y) / (double)(right.x - left.x); + double c = (double)left.y - ((double)left.x * m); + + for (int i = left.x; i <= right.x; i++) { + int y = (int)(m * i + c); + if (y > 0) { + SDL_RenderDrawLine(ren, i, y, i, end); + } + } + + SDL_SetRenderDrawColor(ren, 0,0,0, 255); +} + +// draw collision poly +void draw_collision_poly(SDL_Renderer* ren, Body *body) { + if (!SHOWCOLLISION) { + return; + } + + if (body->colliding) { + SDL_SetRenderDrawColor(ren, 255, 0, 0, 255); + } else { + SDL_SetRenderDrawColor(ren, 0, 255, 0, 255); + } + + // draw strings + for (int i = 0; i < body->num_strings; i++) { + if (!body->strings[i].attached) { + continue; + } + Vect B; + B = in_view(body->position); + Vect E; + E = in_view(body->strings[i].end_point); + SDL_RenderDrawLine(ren, B.x, B.y, E.x, E.y); + } + + if (body->collision_poly_size == 1) { + SDL_Rect dot; + Vect V; + V.x = body->collision_poly[0].x; + V.y = body->collision_poly[0].y; + Vect T = in_view(V); + dot.x = T.x; + dot.y = T.y; + dot.w = 4; + dot.h = 4; + SDL_RenderDrawRect(ren, &dot); + SDL_RenderFillRect(ren, &dot); + } else if (body->collision_poly_size == 2) { + int x_st = body->collision_poly[0].x -viewport_pos.x; + int y_st = body->collision_poly[0].y -viewport_pos.y; + int x_en = body->collision_poly[1].x -viewport_pos.x; + int y_en = body->collision_poly[1].y -viewport_pos.y; + SDL_RenderDrawLine(ren, x_st, y_st, x_en, y_en); + } else if (body->collision_poly_size > 2) { + int x_st = body->collision_poly[0].x -viewport_pos.x; + int y_st = body->collision_poly[0].y -viewport_pos.y; + int x_en, y_en; + for (int i = 1; i < body->collision_poly_size; i++) { + x_en = body->collision_poly[i].x -viewport_pos.x; + y_en = body->collision_poly[i].y -viewport_pos.y; + SDL_RenderDrawLine(ren, x_st, y_st, x_en, y_en); + x_st = x_en; + y_st = y_en; + } + x_en = body->collision_poly[0].x-viewport_pos.x; + y_en = body->collision_poly[0].y-viewport_pos.y; + SDL_RenderDrawLine(ren, x_st, y_st, x_en, y_en); + } + SDL_SetRenderDrawColor(ren, 0,0,0, 255); +} + +// draw indicator of forces acting on an object +void draw_forces(SDL_Renderer *ren, Body *body) { + if (!SHOWFORCES) { + return; + } + Vect start; + start.x = (int)body->position.x -viewport_pos.x; + start.y = (int)body->position.y -viewport_pos.y; + + Vect F; + for (int i = 0; i < body->num_motors; i++) { + if (!get_motor_active(body, i)) { + continue; + } + F.x += body->motors[i].x; + F.y += body->motors[i].y; + Vect end = vect_add(start, F); + SDL_SetRenderDrawColor(ren, 255,0,0, 255); + SDL_RenderDrawLine(ren, start.x, start.y, start.x, end.y); + SDL_SetRenderDrawColor(ren, 0,0,255, 255); + SDL_RenderDrawLine(ren, start.x, start.y, end.x, start.y); + } + + SDL_SetRenderDrawColor(ren, 0,0,0, 255); +} + +void draw_wall(SDL_Renderer *ren, Wall *wall) { + SDL_SetRenderDrawColor(ren, 120, 85, 188, 255); + int x_st, x_en, y_st, y_en; + x_st = wall->nodes[0].x + wall->physics->position.x -viewport_pos.x; + y_st = wall->nodes[0].y + wall->physics->position.y -viewport_pos.y; + for (int i = 1; i < wall->numNodes; i++) { + x_en = wall->nodes[i].x + wall->physics->position.x -viewport_pos.x; + y_en = wall->nodes[i].y + wall->physics->position.y -viewport_pos.y; + SDL_RenderDrawLine(ren, x_st, y_st, x_en, y_en); + x_st = x_en; + y_st = y_en; + } + x_en = wall->nodes[0].x + wall->physics->position.x -viewport_pos.x; + y_en = wall->nodes[0].y + wall->physics->position.y -viewport_pos.y; + SDL_RenderDrawLine(ren, x_st, y_st, x_en, y_en); + + SDL_SetRenderDrawColor(ren, 0,0,0, 255); +} + +int distance_colour(int x, int y, int x2, int y2, int blackpoint) { + + int dist = (int) sqrt((x2 - x)*(x2 - x) + (y2 - y)*(y2 - y) ); + + if (dist == 0) { + return 255; + } + if (dist > blackpoint) { + return 0; + } + + int frac = blackpoint / dist; + +// int frac = 255 * (int) sin((double)dist / blackpoint); + if (frac > 255) { + return 255; + } + + return frac; +} + +void new_update_viewport(Body const * const pl) { + int const xmargin = 100; // pixels + int const ymargin = 100; // pixels + int const xmovmagin = width / 4; + int const ymovmagin = height / 3; + double const xrange = width - (2 * xmargin); + double const yrange = height - (2 * ymargin); + double const x_max = 2; + double const y_max = 3; + static Vect target = {}; + static double dirchange = 0; + static Vect last_plpos = {}; + if (last_plpos.x == 0 && last_plpos.y == 0) + last_plpos = pl->position; + + static Vect lmove = {}; + + Vect iv = in_view(pl->position); + + Vect left_pos = {0, 2 * height / 4}; + left_pos = vect_add(pl->position, vect_scalar(left_pos, -1)); + + Vect right_pos = {7 * (width / 8), 2 * height / 4}; + right_pos = vect_add(pl->position, vect_scalar(right_pos, -1)); + + bool recover = false; + + Vect screen_dist = vect_add(in_view(pl->position), vect_scalar(lmove, -1)); + + static double swap = 0; + if (iv.x > width + || (iv.x < 0 + || iv.y > height + || iv.y < 0)) + recover = true; + + Vect delta = vect_add(vect_scalar(pl->position, -1), last_plpos); + double dist_change = vect_mag(delta); + if ((recover) //|| vect_mag((Vect){pl->vel.x, 0}) > 0.4 + || (dist_change > width / 100)) { + if (iv.x < width / 6) { + target = right_pos; + } else { + target = left_pos; + } + lmove = target; + + last_plpos = pl->position; + } + + if (recover) + viewport_pos = target; + // emergency + + /*if (!(iv.x > width / 3 && iv.x < (2 * (width / 3)))) {*/ + /*if (!(iv.y > height / 3 && iv.y < (2 * (height / 3)))) {*/ + + /*if ( true ||*/ + /*( in_view(pl->position).x > xmargin + xrange */ + /*|| in_view(pl->position).x < xmargin)*/ + /*) */ + + /*{*/ + + + /*double m = (width / 2) / (2.0 * x_max);*/ + /*double x = m * v;*/ + /*target.x = pl->position.x - x;*/ + + + /*}*/ + + //target.y = pl->position.y - height/2; // + y; + + /*}*/ + /*}*/ + + double v = pl->vel.x; + if (v > x_max) + v = x_max; + if (v < -x_max) + v = -x_max; + if (v < 0) + v = -v; + + // move towards target a set proportion + Vect urgency = vect_add(target, vect_scalar(pl->position, -1)); + Vect p = vect_add(target, vect_scalar(viewport_pos, -1)); + + double proportion = (sigmoid(time_delta) / 100); + proportion = 2 * (pl->vel.x * pl->vel.x / (width)); + proportion = proportion < 0 ? -proportion : proportion; + + proportion > 0.4 ? proportion = 0.4 : 0; + proportion < 0 ? proportion = 0.000001 : 0; + + p = vect_scalar(p, proportion); + viewport_pos = vect_add(viewport_pos, p); + + +} + +void update_viewport(Body *pl) { + float xmargin = 0.5; + float ymargin = 0.2; + + if (pl->position.x - viewport_pos.x > (1-xmargin) * width) { + viewport_pos.x = - ((1-xmargin) * width - pl->position.x); + } + + if (pl->position.x - viewport_pos.x < xmargin * width) { + viewport_pos.x = -(xmargin * width - pl->position.x); + } + + if (pl->position.y - viewport_pos.y > (1-ymargin) * height) { + viewport_pos.y = -((1-ymargin) * height - pl->position.y); + } + + if (pl->position.y - viewport_pos.y < ymargin * height) { + viewport_pos.y = -(ymargin * height - pl->position.y); + } +} + +void accel_update_viewport(Body *pl) { + const double accel = 0.0001; + const float xmargin = 0.5; + const float ymargin = 0.2; + + static Vect target_pos = (Vect) {.x=0, .y=0}; + static Vect vel = (Vect) {.x=0.0, .y=0.0}; + + + if (pl->position.x - viewport_pos.x > (1-xmargin) * width) { + target_pos.x = - ((1-xmargin) * width - pl->position.x); + } + + if (pl->position.x - target_pos.x < xmargin * width) { + target_pos.x = -(xmargin * width - pl->position.x); + } + + if (pl->position.y - target_pos.y > (1-ymargin) * height) { + target_pos.y = -((1-ymargin) * height - pl->position.y); + } + + if (pl->position.y - target_pos.y < ymargin * height) { + target_pos.y = -(ymargin * height - pl->position.y); + } + + viewport_pos = target_pos; + return; + + Vect delta = vect_add(target_pos, vect_scalar(viewport_pos, -1)); + printf("d %f %f\n", delta.x, delta.y); + printf("d %f\n", vect_mag(delta)); + + if ((vect_mag(delta) < 0.001 )) { + return; + } + + Vect dir = vect_scalar(delta, 1/vect_mag(delta)); + Vect vel_delta = vect_scalar(dir, accel * time_delta); + vel = vect_add(vel, vel_delta); + + Vect pos_delta = vect_scalar(vel, time_delta/2); + + viewport_pos = vect_add(viewport_pos, pos_delta); +} + +double interpolate_h(Vect left, Vect right, double x) { + static Vect v; + + double m = (double)(right.y - left.y) / (double)(right.x - left.x); + double c = (double)left.y - ((double)left.x * m); + + return x * m + c; +} + +void draw_environment(SDL_Renderer * ren, const struct environment *scene) { + struct colour c1 = scene->colours.bg; + struct colour c2 = scene->colours.fg1; + struct colour c3 = scene->colours.fg2; + struct colour c4 = scene->colours.fg3; + + /*c1->r = 255;*/ + /*c2->g = 255;*/ + /*c3->b = 255;*/ + /*c4->g = 255;*/ + /*c4->b = 255;*/ + + // background colour + SDL_SetRenderDrawColor(ren, c1.r, c1.g, c1.b, 255); + /*SDL_SetRenderDrawColor(ren, 255, 0, 0, 255);*/ + + SDL_Rect rect; + rect.x = 0; + rect.y = 0; + rect.w = width; + rect.h = height; + SDL_RenderFillRect(ren, &rect); + +/* + * double m = (double)(right.y - left.y) / (double)(right.x - left.x); + * double c = (double)left.y - ((double)left.x * m); + * + * for (int i = left.x; i <= right.x; i++) { + * int y = (int)(m * i + c); + * if (y > 0) { + * SDL_RenderDrawLine(ren, i, y, i, end); + * } + * } + */ + + int k = 0; +// printf("from 0 to (room tiles) %d - 1, res %d width %d\n", E_ROOM_TILES, E_ROOM_RES, E_ROOM_WIDTH); + for (int i = 0; i < E_ROOM_TILES - 1; i++) { + for (int j = 0; j < E_ROOM_RES; j++) { + k++; + Vect *left; + Vect *right; + + int x = (i * E_ROOM_RES) + j; + if (x - viewport_pos.x > width) { + break; + } + if (x - viewport_pos.x < 0) { + continue; + } + + left = arlst_get(&scene->ceil, i); + right = arlst_get(&scene->ceil, i+1); + + int y0 = interpolate_h(*left, *right, x) - viewport_pos.y; + + left = arlst_get(&scene->bg1, i); + right = arlst_get(&scene->bg1, i+1); + + int y1 = interpolate_h(*left, *right, x) - viewport_pos.y; + + left = arlst_get(&scene->bg2, i); + right = arlst_get(&scene->bg2, i+1); + + int y2 = interpolate_h(*left, *right, x) - viewport_pos.y; + + left = arlst_get(&scene->floor, i); + right = arlst_get(&scene->floor, i+1); + + int y3 = interpolate_h(*left, *right, x) - viewport_pos.y; + + x = x - viewport_pos.x; + /*printf("x: %d, ", x);*/ + /*printf("y: %d %d %d %d\n", y0, y1, y2, y3);*/ + + // 3. bg2 to floor + SDL_SetRenderDrawColor(ren, c4.r, c4.g, c4.b, 255); + SDL_RenderDrawLine(ren, x, y2, x, y3); + + // 1. ceil to bg1 + SDL_SetRenderDrawColor(ren, c2.r, c2.g, c2.b, 255); + SDL_RenderDrawLine(ren, x, y0, x, y1); + + // 2. bg1 to bg2 + SDL_SetRenderDrawColor(ren, c3.r, c3.g, c3.b, 255); + SDL_RenderDrawLine(ren, x, y1, x, y2); + + + + } + } +} + + +void draw_text(SDL_Renderer *rend, Vect pos, struct colour colour, char *text) { + static SDL_Renderer *ren = NULL; + static TTF_Font* font = NULL; + if (!font) { + font = TTF_OpenFont("res/TerminusTTF.ttf", 44); + const char *err = SDL_GetError(); + if (err) { + printf("%s\n", err); + } + } + if (!ren) { + ren = rend; + } + + SDL_Colour sdl_col = {colour.r, colour.g, colour.b, 255}; + SDL_Rect position = {.x = pos.x, .y = pos.y}; + + SDL_SetRenderDrawColor(ren, colour.r, colour.g, colour.b, 255); + SDL_Surface *surf = TTF_RenderText_Solid(font, text, sdl_col); + + SDL_Texture *texture = SDL_CreateTextureFromSurface(ren, surf); + SDL_QueryTexture(texture, NULL, NULL, &position.w, &position.h); + SDL_RenderCopy(ren, texture, NULL, &position); + + SDL_DestroyTexture(texture); + SDL_FreeSurface(surf); + +} + + +void draw_level_chooser_tbox(SDL_Renderer *ren) { + + char string[250]; + snprintf(string, 250,"GOTO LEVEL: %s", gameui.currently_bound_textbox->text_input); + + const struct environment *scene = get_scene_watch(); + + Vect pos = {.x= width - (width / 4), .y = height - (height / 10)}; + draw_text(ren, pos, scene->colours.fg1, string); +} + + +int draw_end_screen(SDL_Renderer *ren) { + const struct environment *scene = get_scene_watch(); + + struct colour bg = scene->colours.bg; + struct colour colour = scene->colours.fg1; + struct colour_pallete pal = scene->colours; + + SDL_SetRenderDrawColor(ren, bg.r, bg.g, bg.b,255); + + SDL_RenderClear(ren); + + + SDL_SetRenderDrawColor(ren, pal.fg2.r, pal.fg2.g, pal.fg2.b,255); + for (int i = width - 300; i < width; i++) { + SDL_RenderDrawLine(ren, i, height, width, height - i); + if (!(i % 240)) { + SDL_SetRenderDrawColor(ren, pal.fg3.r, pal.fg3.g, pal.fg3.b,255); + } + if (!(i % 350)) { + SDL_SetRenderDrawColor(ren, pal.fg1.r, pal.fg1.g, pal.fg1.b,255); + } + } + + + int margin = height / 10; + // Vect position = {.x = width - width / 10, .y = height - margin}; + + long fastest = draw_watch.best_time; + + int minutes = level_time / 60000; + float seconds = level_time * 0.001; + char time_string[250]; + + + char * end_str = "level over."; + + Vect position = {.x = width/4, .y = height / 4}; + draw_text(ren, position, colour, end_str); + position.y += 60; + + char level_string[250]; + snprintf(level_string, 250,"course: %d", level); + draw_text(ren, position, colour, level_string); + position.y += 40; + + char fastest_time[250]; + minutes = fastest / 60000; + seconds = fastest * 0.001; + if (fastest > 0) { + snprintf(fastest_time, 250, "old best time: %d:%.4f", minutes, seconds); + draw_text(ren, position, colour, fastest_time); + position.y += 40; + } + + /* + char qual_str[250]; + snprintf(qual_str, 250, "physics quality: %d", quality); + draw_text(ren, position, colour, qual_str); + position.y += 40; + */ + + + minutes = level_time / 60000; + seconds = level_time * 0.001; + if (level_time > fastest) { + // base time not set yet. + snprintf(time_string, 250, "TIME: %d:%.4f", minutes, seconds); + } else { + snprintf(time_string, 250, "NEW BEST TIME: %d:%.4f", minutes, seconds); + } + + Vect position2 = {.x = position.x + width/3, .y = 60 + height / 4}; + + if (fastest > 0) { + //position.y += 40; + long diff = level_time - fastest; + char diff_str[250]; + minutes = diff / 60000; + seconds = diff * 0.001; + if (minutes > 0) { + snprintf(diff_str, 250, "DIFF: %d:%.4f", minutes, fabs(seconds)); + } else { + snprintf(diff_str, 250, "DIFF: %.4f s", seconds); + } + + draw_text(ren, position2, colour, diff_str); + position2.y += 40; + } + + draw_text(ren, position2, colour, time_string); + + position.y += 200; + char * ct_str = "press any key to continue"; + draw_text(ren, position, colour, ct_str); + + SDL_RenderPresent(ren); + return 0; +} + + +void draw_level_time(SDL_Renderer *ren, const struct environment * e) { + + int margin = height / 10; + + struct colour colour = e->colours.fg1; + + int minutes = level_time / 60000; + float seconds = level_time * 0.001; + char time_string[250]; + snprintf(time_string, 250, "time: %d:%.4f", minutes, seconds); + + char level_string[250]; + snprintf(level_string, 250,"course: %d", level); + + Vect position = {.x = margin, .y = height - margin}; + draw_text(ren, position, colour, level_string); + position.y += 40; + draw_text(ren, position, colour, time_string); + +} + +void draw_pause_screen(SDL_Renderer *ren, struct environment *e) { + + char *te[] = { "GAME PAUSED.", + "controls", + "________", + "pause/unpause: esc", + "grapple: right click", + "grapple and pull: left click", + "go to level: g", + "mute/unmute: m", + "quit: q" + }; + + struct colour textc = e->colours.fg1; + struct colour bg = e->colours.bg; + + SDL_SetRenderDrawColor(ren, bg.r, bg.g, bg.b, 255); + int boxwidth = 360; + int boxheight = 348; + int box_x = (2 * width / 3) - (boxwidth / 2); + int box_y = (height - boxheight) / 2; + SDL_Rect r = {.w = boxwidth, .h = boxheight, .x = box_x, .y = box_y}; + SDL_RenderFillRect(ren, &r); + + Vect p = {.x = box_x + 10, .y = box_y + 10}; + draw_text(ren, p, textc, te[0]); + p.y += 60; + + draw_text(ren, p, textc, te[1]); + p.y += 5; + draw_text(ren, p, textc, te[2]); + p.y += 35; + + draw_text(ren, p, textc, te[3]); + p.y += 40; + + draw_text(ren, p, textc, te[4]); + p.y += 40; + + draw_text(ren, p, textc, te[5]); + p.y += 40; + + draw_text(ren, p, textc, te[6]); + p.y += 40; + + draw_text(ren, p, textc, te[7]); + p.y += 40; + + draw_text(ren, p, textc, te[8]); + p.y += 40; + +} + +void redraw_buffer(SDL_Renderer * ren) { + static int mousex = 0; + static int mousey = 0; + static int newmousex = 0; + static int newmousey = 0; + static SDL_Point *bgPixels[256]; + static int numpixels[256]; + + /*if (!width && !height) {*/ + /*for (int i = 0; i < 256; i++) {*/ + /*bgPixels[i] = malloc(sizeof(SDL_Point) * width * height);*/ + /*memset(bgPixels[i], 0, sizeof(SDL_Point) * width * height);*/ + /*memset(numpixels, 0, (sizeof(int) * 256));*/ + /*}*/ + /*}*/ + + int col = 0; + Body lplayer; + + if (SDL_LockMutex(player.physics->lock) == 0){ + lplayer = *player.physics; + update_viewport(&lplayer); + } else { + return; + } + + static struct timespec last = {}; + static struct timespec now; + now = get_now_d(); + time_delta = now.tv_nsec - last.tv_nsec; + time_delta *= 0.000001; // convert to ms from ns + + last = now; + + //SDL_GetMouseState(&newmousex, &newmousey); + + const struct environment* scene = get_scene_watch(); + draw_environment(ren, scene); + draw_level_time(ren, scene); + SDL_UnlockMutex(player.physics->lock); + + for (int i=0; i < world.items.size; i++) { + world_thing thing; + thing = world.get(i); + + switch (thing.kind) { + case STATIC_WALL_W: + draw_wall(ren, thing.wall); + draw_collision_poly(ren, thing.wall->physics); + continue; + case FLOOR: + for (int i = 0; i < thing.floor->numPolys; i++) { + draw_floor(ren, &thing.floor->polys[i], true); + draw_collision_poly(ren, thing.floor->polys[i].physics); + } + continue; + case CEILING: + for (int i = 0; i < thing.floor->numPolys; i++) { + draw_floor(ren, &thing.floor->polys[i], false); + draw_collision_poly(ren, thing.floor->polys[i].physics); + } + continue; + case ROOM_W: + for (int i = 0; i < thing.room->ceil.numItems; i++) { + draw_collision_poly(ren, thing.room->ceil.items[i]); + } + for (int i = 0; i < thing.room->floor.numItems; i++) { + draw_collision_poly(ren, thing.room->floor.items[i]); + } + default: + continue; + } + } + + draw_player(ren, lplayer.position.x, lplayer.position.y, + lplayer.colliding); + draw_collision_poly(ren, &lplayer); + draw_forces(ren, &lplayer); + + if (gameui.currently_bound_textbox) { + draw_level_chooser_tbox(ren); + } + + if (game_paused) { + SDL_Rect r = {.x = 0, .y = 0, .w = width, .h = height}; + SDL_SetRenderDrawColor(ren, 0, 0, 0, 90); + SDL_RenderFillRect(ren, &r); + if (in_game) + draw_pause_screen(ren, scene); + } + + /*if (newmousex != mousex || newmousey != mousey) {*/ /*mousey = newmousey;*/ + /*mousex = newmousex;*/ + /*for (int i = 0; i < 256; i++) {*/ + /*memset(bgPixels[i], 0, sizeof(SDL_Point) * width * height);*/ + /*memset(numpixels, 0, (sizeof(int) * 256));*/ + /*}*/ + /*for (int i=0; i < width; i++) {*/ + /*for (int j = 0; j < height; j++) {*/ + /*col = distance_colour(i, j, mousex, mousey, 10000);*/ + + /*SDL_Point *row = bgPixels[col];*/ + /*SDL_Point point;*/ + /*point.x = i;*/ + /*point.y = j;*/ + /*row[numpixels[col]] = point;*/ + /*numpixels[col] += 1;*/ + + /*}*/ + /*}*/ + /*}*/ + + /*for (int i = 0; i < 255; i++) {*/ + /*SDL_Point *row = bgPixels[i];*/ + /*col = i;*/ + /*SDL_SetRenderDrawColor(ren, col, col, col, 255);*/ + /*SDL_RenderDrawPoints(ren, row, numpixels[i]);*/ + /*}*/ + +} + diff --git a/build-android/src/draw.h b/build-android/src/draw.h new file mode 100644 index 0000000..e98d4c7 --- /dev/null +++ b/build-android/src/draw.h @@ -0,0 +1,42 @@ +#ifndef _DEFDRAW +#define _DEFDRAW + +#include +#include + +#include "colours.h" +#include "vect.h" +#include "environment.h" +#include "game.h" + +#define SHOWCOLLISION 0 +#define SHOWFORCES 0 + +typedef enum { + PLAYER +} draw_type; + +void render_texture_at(struct SDL_Renderer * ren, struct SDL_Texture * texture,int x, int y) ; +/* draw a texture at x.y */ + +SDL_Texture * load_image(SDL_Renderer * ren, char fname[]); +/* Load an image into a texture */ + +void draw_player(SDL_Renderer * ren, int x, int y, bool red); +/* draw the player as a coloured rect */ + +//void queue_draw_item(void * object, draw_type kind); +void add_to_view(draw_type kind, void * object); + +void redraw_buffer(SDL_Renderer * ren); + +void draw_text(SDL_Renderer *ren, Vect pos, struct colour colour, char *text); + +int draw_end_screen(SDL_Renderer *ren); + +extern Vect viewport_pos; +extern int width, height; +extern bool high_contrast_mode; + + +#endif diff --git a/build-android/src/environment.c b/build-android/src/environment.c new file mode 100644 index 0000000..c496f56 --- /dev/null +++ b/build-android/src/environment.c @@ -0,0 +1,244 @@ +#include "environment.h" +#include "game.h" +#include "types.h" + +struct environment * environment_watch = NULL; + +const struct environment* get_scene_watch(void) { + return environment_watch; +} + +double linear_interpolate(double a0, double a1, double w) { + return (1.0f - w) * a0 + w * a1; +} + +int destroy_environment(struct environment *e) { + /*Vect position;*/ + /*ArrayList ceil; // doubles*/ + /*ArrayList floor; // doubles*/ + /*ArrayList bg1; // doubles*/ + /*ArrayList bg2; // doubles*/ + /*struct colour_pallete colours;*/ + /*struct physics_collection physics;*/ + + arlst_destroy(&e->ceil); + arlst_destroy(&e->floor); + arlst_destroy(&e->bg1); + arlst_destroy(&e->bg2); + + return 0; +} + +double perlin(Vect coordinate) { + Vect a = coordinate; + Vect b = coordinate; + a.x = (int)a.x; + a.y = (int)a.y; + b.x = a.x + 1; + b.y = a.y + 1; + + double sx = coordinate.x - a.x; + double sy = coordinate.y - a.y; + + Vect d = b; + d.y = a.y; + + double n0 = vect_dot(a, coordinate); + double n1 = vect_dot(d, coordinate); + + double ix0 = linear_interpolate(n0, n1, sx); + + Vect c; + c.x = a.x; + c.y = b.y; + + n0 = vect_dot(c, coordinate); + n1 = vect_dot(b, coordinate); + double ix1 = linear_interpolate(n0, n1, sy); + + return linear_interpolate(ix0, ix1, sy); +} + +struct colour_pallete get_pallete_high_contrast(void) { + struct colour white = {.r = 255, .g=255,.b=255, .sp=CS_RGB}; + struct colour black = {.r = 0, .g=0,.b=0, .sp=CS_RGB}; + struct colour_pallete p = {.bg = black, .fg1 = white, .fg2 = white, .fg3 = white}; + return p; +} + + +struct colour_pallete get_pallete (int seed) { + struct colour base; + + if (high_contrast_mode) + return get_pallete_high_contrast(); + + int n = seed; + int red = get_rand(&n) % 255; + int blue = get_rand(&n) % 255; + int green = get_rand(&n) % 255; + + base.r = red; + base.g = green; + base.b = blue; + + struct colour c1 = get_hsv(base); + c1 = get_hsv(base); + struct colour_pallete cols; + + c1.l = 0.1; + cols.bg = get_rgb(c1); + c1.l += 0.3; + + c1.h += 30; + cols.fg1 = get_rgb(c1); + + c1.h += 30; + cols.fg2 = get_rgb(c1); + + c1.h += 30; + cols.fg3 = get_rgb(c1); + + return cols; + + struct colour *adj = get_adjacent(base, 20, 4); + ArrayList l = new_arlst_wfree(4, free); + + for (int i = 0; i < 4; i++) { + arlst_add(&l, adj + i); + } + +} + +int comp(const void *one, const void *two) { + return *(int*)one >= *(int*)two; +} + +struct environment get_scene_at(Vect coordinate, int seed) { + // TODO: Environment needs to be shared between threads + // - this implementation duplicates it: each thread that calls + // get_scene_at gets manages its state in its own static vairables in + // this function but share pointers to the malloced environment object- + // it is thoroughly broken. + // - Fix by having a heap allocated environment object that the draw thread + // (T0) can watch and the physics thread (T3) can modify + // - the entry point can either be through a function returning + // a pointer or just a global const pointer + // + // basic cache for the last room generated + static bool init; + static Vect last_room; + static struct environment e; + static int oldseed; + Vect room_coord; + room_coord.x = (int)coordinate.x - (int)coordinate.x % E_ROOM_WIDTH; + room_coord.y = 0; + + if (init && room_coord.x == last_room.x && oldseed == seed) { + return e; + } else if (init) { + destroy_environment(&e); + } + last_room = room_coord; + oldseed = seed; + + e.floor = new_arlst_wfree(E_ROOM_TILES, free); + e.ceil = new_arlst_wfree(E_ROOM_TILES, free); + e.bg1 = new_arlst_wfree(E_ROOM_TILES, free); + e.bg2 = new_arlst_wfree(E_ROOM_TILES, free); + + e.colours = get_pallete(seed); + + Vect bit; + bit.y = 100 * seed; + Vect pos; + + int n = seed; + get_rand(&n); + + Vect node; + node.y = 0; + + for (int i = 0; i < E_ROOM_WIDTH; i += E_ROOM_RES) { + bit.x = room_coord.x + i; + bit.y = seed; + node.x = bit.x; + + int r1 = 1.5*(get_rand(&n) % 500) - (get_rand(&n) % 100); + int r2 = 1.5*(get_rand(&n) % 200) - (get_rand(&n) % 200); + int r3 = (get_rand(&n) % 100) - (get_rand(&n) % 500); + int r4 = 50; + + int r[4] = {r1, r2, r3, r4}; + qsort(r, 4, sizeof(int), comp); + // r[0] == ceiling + // r[3] == floor + + //node.y += r[0]; + int h[4] = {node.y,node.y,node.y,node.y}; + h[0] += r[0]; + h[1] = h[0] + r[1]; + h[2] += h[0] + r[2]; + h[3] += h[0] + r[3]; + qsort(h, 4, sizeof(int), comp); + +// node.y = fmod(perlin(bit), 3000); + Vect *z = malloc(sizeof(Vect)); + *z = node; + z->y = h[0]; + + arlst_add(&e.floor, z); + + z = malloc(sizeof(Vect)); + *z = node; +// z->y += r[1]; + z->y = h[1]; + arlst_add(&e.bg1, z); + + z = malloc(sizeof(Vect)); + *z = node; + //z->y += r[2]; + z->y = h[2]; + arlst_add(&e.bg2, z); + + + z = malloc(sizeof(Vect)); + *z = node; +// z->y += r[3]; + z->y = h[3]; + arlst_add(&e.ceil, z); + + } + + Vect *v = arlst_get(&e.floor, 0); + Vect v2 = *(Vect *)arlst_get(&e.ceil, 0); + + Vect v3 = vect_add(v2, vect_scalar(vect_add(*v, vect_scalar(v2, -1)) , 0.5)); + + int d[] = {0, E_ROOM_TILES - 1}; + int a; + for (int i = 0; i <= 1; i++) { + a = d[i]; + v = arlst_get(&e.floor, a); + v->y = v3.x; + v = arlst_get(&e.ceil, a); + v->y = v3.x; + v = arlst_get(&e.bg1, a); + v->y = v3.x; + v = arlst_get(&e.bg2, a); + v->y = v3.x; + } + + // join the end and start together + + if (!init) { + struct environment *ee = malloc(sizeof(struct environment)); + environment_watch = ee; + } + + *environment_watch = e; + + init = true; + + return e; +} diff --git a/build-android/src/environment.h b/build-android/src/environment.h new file mode 100644 index 0000000..aefe92b --- /dev/null +++ b/build-android/src/environment.h @@ -0,0 +1,12 @@ +#ifndef ENVIRO_H +#define ENVIRO_H + +#include "vect.h" +#include "types.h" + +struct environment get_scene_at(Vect coordinate, int seed); +int destroy_environment(struct environment *e); + +const struct environment *get_scene_watch(void); + +#endif diff --git a/build-android/src/game.c b/build-android/src/game.c new file mode 100644 index 0000000..95df00b --- /dev/null +++ b/build-android/src/game.c @@ -0,0 +1,1869 @@ +#include "game.h" +#include "draw.h" +#include "audio.h" +#include "types.h" +#include +#include +#include +#include +#include + +#define FLOOR_THICKNESS 200 +#define MAX_ROPE_GRAB_LEN 80000 +#define MIN_PHYSICS_STEP 6.0 + +#define TIMESTEP_LENGTH 3.0 + +#define BREAKPOINT *(int *)0 = 1; + +GlobWorld world; + +player_st *glob_player; +SDL_Renderer *debug_ren; + + +bool in_game; +bool game_paused = false; +extern bool mute; +int level; +int quality = 100; +long level_time; + +int get_floor_ceiling(); + +#ifdef SCORE_SYSTEM +struct sg_times_list save_times_lst; +#endif + +/* object that draw.c watches */ +struct draw_watcher draw_watch = {}; +/* object that handles ui interactions in game.c and which draw watches */ +struct textbox_info textboxes[1]; + + +struct ui_state gameui = {}; + +struct timespec last_tick; +double time_remaining = 0; +struct timespec get_now(); + +/* array of all the things in the world and their kinds */ +//world_thing *world; + +void startgame(SDL_Renderer * ren) ; + +void process_keydown(SDL_Keysym key); +void process_keyup(SDL_Keysym key); + +void add_to_world(world_thing thing); +void set_motor_timeout(Body *thing, int motorID, uint32_t timeout); +void set_motor_status(Body *thing, int motorID, bool run); +void stop_pull_rope(void); + +player_st player; + +// local +void set_motor_newtons(Body *thing, int motorID, double x, double y); +void set_motor_max_velocity(Body *thing, int motorID, double max); +void get_new_physics(Body **phys); +void ratcheted_winch_motor_update(Motor* motor); +void winch_motor_update (struct motorstruct *motor); +void load_level(); + +void reset_textbox(struct textbox_info *tb) { + tb->text_input_bufpos = 0; + memset(tb->text_input, 0, sizeof(tb->text_input)); +} + +void write_to_textbox(struct textbox_info *tb, char c) { + if (tb->text_input_bufpos >= sizeof(tb->text_input)) { + return; + } + + tb->text_input[tb->text_input_bufpos++] = c; +} + +void delete_from_textbox(struct textbox_info *tb) { + if (tb->text_input_bufpos > 0) + tb->text_input[--tb->text_input_bufpos] = 0; +} + +// move the collision poly to the position of the player +void default_update_collision_poly(Body *body) { + for (int i=0; i < body->collision_poly_size; i++) { + + double x = body->collision_shape[i].x + body->position.x; + double y = body->collision_shape[i].y + body->position.y; + + body->collision_poly[i].x = x; + body->collision_poly[i].y = y; + } +} + +void new_level_tb_close_callback(struct textbox_info*textbox, void*callback) { + for (int i = 0; i < textbox->text_input_bufpos; i++) { + if (textbox->text_input[i] < '0' || textbox->text_input[i] > '9') + return; + } + + int lid = atoi(textbox->text_input); + + gameui.goto_new_level = lid; + +} + +void level_timer_update(bool reset) { + static int start_point = 0; + long now = SDL_GetTicks(); + if (reset) { + level_time = 0; + start_point = now; + } else { + level_time = now - start_point; + } +} + + +int long_comparator(const void *a, const void *b, void *non) { + long A = *(long *)a; + long B = *(long *)b; + + return A - B; +} + +int win_long_comparator(void *context_unused, const void *a, const void *b) { + long A = *(long *)a; + long B = *(long *)b; + + return A - B; +} + +#ifdef SCORE_SYSTEM +void sort_times(void) { +#ifdef __linux__ + qsort_r(save_times_lst.times, save_times_lst.size, sizeof(long), long_comparator, NULL); +#elif WIN32 + qsort_s(save_times_lst.times, save_times_lst.size, sizeof(long), win_long_comparator, NULL); +#endif +} + +long get_best_time(void) { + save_times_lst.sort(); + if (save_times_lst.size > 0) + return save_times_lst.times[0]; + return -1; +} + + +int load_score_times(char *filename) { + + char *fn; + char lvl_str[250]; + snprintf(lvl_str, 250, "%d", level); + int fnlen = strlen(filename) + strlen(lvl_str) + 3; + fn = malloc(fnlen); + snprintf(fn, fnlen, "%s-%s", filename, lvl_str); + + //printf("%d, lvl_str %s fn %s\n", level, lvl_str, fn); + + FILE *f = fopen(fn, "r"); + // unload existing score times + if (save_times_lst.times) { + write_times(); + free(save_times_lst.times); + free(save_times_lst.filename); + } + + if (f) { + // get file size + fseek(f, 0L, SEEK_END); + long sz = ftell(f); + fseek(f, 0L, SEEK_SET); + char *text = calloc((sz + 1), (sizeof(char))); + + // read file + fread(text,sizeof(char),sz, f); + fclose(f); + + int count = 1; + for (int i = 0; i < strlen(text); i++) { + if (text[i] == '\n') { + count++; + } + } + + long *times = malloc(count * sizeof(long)); + char *saveptr; + + // extract times + char * token = strtok_r(text, "\n", &saveptr); + int i = 0; + while (token != NULL) { + if (strlen(token) > 2) { + times[i] = atol(token); + i++; + } + token = strtok_r(NULL, "\n", &saveptr); + } + /* here somweher? */ + + struct sg_times_list stl = {.size=i, .capacity = count, .times=times, .sort=sort_times}; + save_times_lst = stl; + sort_times(); + free(text); + } else { + perror("loading times"); + save_times_lst = (struct sg_times_list){.size=0, .capacity = 5, .times=calloc(5, sizeof(long)), .sort=sort_times}; + } + + save_times_lst.filename = fn; + return 0; +} + +int add_time(long time) { + if (save_times_lst.capacity == save_times_lst.size + 5) { + long newcap = save_times_lst.capacity * 2; + save_times_lst.times = realloc(save_times_lst.times, newcap); + save_times_lst.capacity = newcap; + } + save_times_lst.times[save_times_lst.size] = time; + save_times_lst.size++; + + return 0; +} + +int write_times(void) { + FILE *f = fopen(save_times_lst.filename, "w"); + if (!f) { + perror("Saving game"); + return 1; + } + for (int i = 0; i < save_times_lst.size; i++) { + fprintf(f, "%ld\n", save_times_lst.times[i]); + } + fclose(f); + return 0; +} + +#endif // SCORE_SYSTEM + + +// collision poly size must be 2x shape_size + 1 +void cast_update_collision_poly(Body *body) { + for (int i=0; i < body->collision_shape_size; i++) { + double x = body->collision_shape[i].x + body->position.x; + double y = body->collision_shape[i].y + body->position.y; + + body->collision_poly[i].x = x; + body->collision_poly[i].y = y; + } + for (int i=body->collision_shape_size - 1; i >= 0; i--) { + double x = body->collision_shape[i].x + body->next_position.x; + double y = body->collision_shape[i].y + body->next_position.y; + int k = body->collision_shape_size + i; + + body->collision_poly[k].x = x; + body->collision_poly[k].y = y; + } + + /*int i=body->collision_shape_size - 1;*/ + /*double x = body->collision_shape[i].x + body->next_position.x;*/ + /*double y = body->collision_shape[i].y + body->next_position.y;*/ + + /*body->collision_poly[body->collision_shape_size - 1 + i].x = x; */ + /*body->collision_poly[body->collision_shape_size - 1 + i].y = y; */ + + + /*body->collision_poly[(body->collision_poly_size - 1) * 2 + 1] = body->collision_poly[(body->collision_poly_size - 1) * 2 + 1];*/ + /*body->collision_poly[(body->collision_poly_size - 1) * 2 + 2] = body->collision_poly[(body->collision_poly_size - 1)];*/ +} + +void default_motor_curve(Motor *motor) { + // constant + return; +} + +world_thing world_getter(int i) { + world_thing *item = arlst_get(&world.items, i); + return *item; +} + + +FloorPoly* generate_floor_simple(int num_polys, bool extend_down, int st_height) { + FloorPoly *floor = calloc(num_polys, sizeof(FloorPoly)); + Vect last, next; + last.x = 10; + last.y = st_height; + + int n = 1; + for (int i = 0; i < num_polys; i++) { + double run = (get_rand(&n) % 900); + double rise = (get_rand(&n) % 100) - (get_rand(&n) % 100); + next.x = last.x + run; + next.y = last.y + rise; + + FloorPoly poly; + poly.left = last; + poly.right = next; + get_new_physics(&poly.physics); + + poly.physics->position = last; + + int offset = extend_down ? FLOOR_THICKNESS : -FLOOR_THICKNESS; + + poly.physics->collision_poly_size = 4; + poly.physics->collision_poly = calloc(4, sizeof(Vect)); + poly.physics->collision_shape = calloc(4, sizeof(Vect)); + + poly.physics->collision_shape[0].x = 0; + poly.physics->collision_shape[0].y = 0; + + poly.physics->collision_shape[1].x = run; + poly.physics->collision_shape[1].y = rise; + + poly.physics->collision_shape[2].x = run; + poly.physics->collision_shape[2].y = rise+offset; + + poly.physics->collision_shape[3].x = 0; + poly.physics->collision_shape[3].y = rise+offset; + + default_update_collision_poly(poly.physics); + + last = next; + floor[i] = poly; + } + + return floor; +} + +// @param uninitialised Body pointer +// @result: malloc and configure a physics thing pointer +void get_new_physics(Body **phys) { + static int uid = 0; + + /* physics */ + Body * physics = malloc(sizeof(Body)); + memset(physics, 0, sizeof(Body)); + + // give it the next uid + physics->uid = uid++; + + physics->dynamics = false; + physics->glob_gravity = false; + physics->glob_friction = 0.0000; + + physics->obj_mass = 100; + //kgs + + physics->motors = malloc(sizeof(Motor) * 100); + memset(physics->motors, 0, sizeof(Motor) * 100); + physics->max_motors = 100; + physics->num_motors = 0; + + // gravity + add_motor(physics, 0.0, 0.0); + // friction + add_motor(physics, 0.0, 0.0); + + clock_gettime(CLOCK_MONOTONIC, &physics->last_advance_time); + + physics->updateCollisionPoly = default_update_collision_poly; + *phys = physics; + return; +} + +/*void updatePlayerCollision(Body *physics) {*/ + /*physics->collision_poly[0].x = physics->x_pos;*/ + /*physics->collision_poly[0].y = physics->y_pos;*/ +/*}*/ + + +int string_update_fixed(String *string) { + return 0; +} + +int string_set_end(String *string, Vect *setting) { + string->end_point = *setting; + return 0; +} + +String *get_fixed_strings(int number) { + String *strings = calloc(number, sizeof(String)); + for (int i = 0; i < number; i++) { + + strings[i].attached = false; + + // takes self (String) and does nothing. + strings[i].update_end_point = string_update_fixed; + + // takes self and vect to set string end point + strings[i].set_end_point = string_set_end; + } + return strings; +} + +player_st get_player(int x, int y) { + /* creates player at given postion and zeroes physics */ + + // player + player_st player; + memset(&player, 0, sizeof(player)); + player.max_walking_speed = 100; + + // physics settings + get_new_physics(&player.physics); + player.physics->dynamics = true; + player.physics->lock = SDL_CreateMutex(); + + player.physics->position.x = x; + player.physics->position.y = y; + player.physics->next_position = player.physics->position; + player.physics->obj_elasticity = 0.5; + player.physics->obj_friction = 0.2; + + // friction (not in use) + player.physics->glob_friction = 40; // drag coef * area + + player.physics->collision_poly_size = 4 + 4; + player.physics->collision_poly = calloc(player.physics->collision_poly_size, sizeof(Vect)); + + player.physics->collision_shape_size = 4; + player.physics->collision_shape = calloc(4, sizeof(Vect)); + + player.physics->updateCollisionPoly = cast_update_collision_poly; + + // swing rope + player.physics->num_strings = 1; + player.physics->max_strings = 1; + player.physics->strings = get_fixed_strings(1); + player.physics->strings->max_length = 210; + + Vect *rect = player.physics->collision_shape; + rect[0].x = -10; rect[0].y = -10; + rect[1].x = 10; rect[1].y = -10; + rect[2].x = 10; rect[2].y = 10; + rect[3].x = -10; rect[3].y = 10; + + // gravity + set_motor_newtons(player.physics, M_GRAVITY, 0.0, + player.physics->obj_mass * 5); + set_motor_status(player.physics, M_GRAVITY, true); + set_motor_timeout(player.physics, M_GRAVITY, 99999999); + + // walking motor + add_motor(player.physics, 0.0, player.physics->obj_mass * 9.81); + set_motor_max_velocity(player.physics, M_PLAYER_WALK, 5); // has to be > grav + set_motor_max_velocity(player.physics, M_GRAVITY, 5); + + // winch motor for string + add_motor(player.physics, 0.0, 0); + player.physics->motors[M_WINCH].update_motor = ratcheted_winch_motor_update; + + return (player); +} + +bool point_point_colcheck(Vect one, Vect two) { + if (one.x == two.x && one.y == two.y) { + return true; + } + return false; +} + +typedef struct { + Vect one; + Vect two; +} Collision; + +bool point_line_colcheck(Vect line[2], Vect point) { + // point is outside the rectangle made by the line + if ((point.x > line[0].x && point.x > line[1].x) + || (point.x < line[0].x && point.x < line[0].x) + || (point.y > line[0].y && point.y > line[0].y) + || (point.y < line[0].y && point.y < line[0].y) + ){ + return false; + } + + double m = (double)(line[1].y - line[0].y) / (double)(line[1].x - line[0].x); + double c = (double)(line[0].y - (m * line[0].x)); + + double y = point.x * m + c; + + // point is in the line +- 1 + if ((int)y == point.y || ((int)y < point.y && (int)y + 1 > point.y)) { + return true; + } + + return false; +} + +double *project_col_poly(Body *shape, Vect V) { + double *proj = calloc(shape->collision_poly_size, sizeof(double)); + for (int i = 0; i < shape->collision_poly_size; i++) { + Vect point; + point.x = shape->collision_poly[i].x; + point.y = shape->collision_poly[i].y; + //double mag = vect_mag(point); +// printf("point: %f %f mag: %f\n", point.x, point.y, vect_mag(point)); +// printf("Vectp: %f %f mag: %f\n", V.x, V.y, vect_mag(V)); + proj[i] = vect_scalar_projection(point, V); + } + double min, max; + max = -99999999; + min = 99999999; + for (int i = 0; i < shape->collision_poly_size; i++) { + if (proj[i] > max) { + max = proj[i]; + } + if (proj[i] < min) { + min = proj[i]; + } + } + double *res = calloc(2, sizeof(double)); + res[0] = min; + res[1] = max; + + free(proj); + return res; +} + +Vect get_normal(Vect start, Vect end) { + Vect norm; + double x = (end.x - start.x); + double y = (end.y - start.y); + norm.x = -y; + norm.y = x; + double len = vect_mag(norm); + norm.x /= len; + norm.y /= len; + + return norm; +} + +// reference: +// http://www.dyn4j.org/2010/01/sat/#sat-inter +// +// TODO: Maybe avoid calculating the centre of the boxes; have body->position +// be the centre and translate the collision box to be around that. It does +// make placing a little more complex but no bother. +bool sat_collision_check(Body *one, Body *two, Vect *translation) { + int num_axes = one->collision_poly_size + two->collision_poly_size; + Vect *axes = calloc(num_axes, sizeof(Vect)); + + Vect end; + Vect start = one->collision_poly[0]; + Vect one_position; + Vect two_position; + + two_position = two->collision_poly[0]; + one_position = one->collision_poly[0]; + + for (int i = 1; i < one->collision_poly_size; i++) { + one_position = vect_add(one_position, one->collision_poly[i]); + end = one->collision_poly[i]; + axes[i-1] = get_normal(start, end); + start = end; + } + + end = one->collision_poly[0]; + axes[one->collision_poly_size - 1] = get_normal(start, end); + + start = two->collision_poly[0]; + for (int i = 1; i < two->collision_poly_size; i++) { + two_position = vect_add(two_position, two->collision_poly[i]); + end = two->collision_poly[i]; + axes[i - 1 + one->collision_poly_size] = get_normal(start, end); + start = end; + } + + end = two->collision_poly[0]; + axes[two->collision_poly_size + one->collision_poly_size - 1] = get_normal(start, end); + + double *proj_one, *proj_two; + proj_one = proj_two = 0; + + Vect min_axis; + double min_overlap = 99999999; + for (int i = 0; i < num_axes; i++) { + // project + if (proj_one) { + free(proj_one); + } + if (proj_two) { + free(proj_two); + } + proj_one = project_col_poly(one, axes[i]); + proj_two = project_col_poly(two, axes[i]); + + if ((proj_one[0] >= proj_two[1]) + || (proj_two[0] >= proj_one[1])) { + free(axes); + free(proj_one); + free(proj_two); + return false; + } else { + double overlap; + double left = proj_one[1] < proj_two[1] ? proj_one[1] : proj_two[1]; + double right = proj_one[0] > proj_two[0] ? proj_one[0] : proj_two[0]; + overlap = left - right; + + // one of the shapes is contained + if ((overlap > (proj_one[1] - proj_one[0]) + || (overlap > (proj_two[1] - proj_two[0])))) { + + double min = proj_one[0] - proj_two[0]; + double max = proj_one[1] - proj_two[1]; + + min = min < 0 ? -min : min; + max = max < 0 ? -max : max; + + overlap += min < max ? min : max; + + } + + if (overlap < min_overlap && overlap > 0) { + min_overlap = overlap; + min_axis = axes[i]; + } + } + } + + // flip the MTV if it is pointing INTO the object + Vect trans; + trans.x = min_overlap * min_axis.x; + trans.y = min_overlap * min_axis.y; + + // https://gamedev.stackexchange.com/questions/27596/implementing-separating-axis-theorem-sat-and-minimum-translation-vector-mtv/27629#27629 + Vect direction; + one_position.x /= one->collision_poly_size; + one_position.y /= one->collision_poly_size; + two_position.x /= two->collision_poly_size; + two_position.y /= two->collision_poly_size; + + /*printf("ONE POS: %f %f", one_position.x, one_position.y);*/ + /*printf("TWO POS: %f %f", two_position.x, one_position.y);*/ + + direction.x = one_position.x - two_position.x; + direction.y = one_position.y - two_position.y; + + double dot = vect_scalar_projection(trans, direction); + + /*printf("DIRECTION: %f %f\n\n", direction.x, direction.y);*/ + /*printf("DOT: %f\n\n", dot);*/ + + if (dot > 0) { + trans = vect_scalar(trans, -1); + } + + // return the MTV + *translation = trans; + + + free(axes); + free(proj_one); + free(proj_two); + return true; +} + +bool check_collision(Body *one, Body *two, Vect *translation) { + int onesize = one->collision_poly_size; + int twosize = two->collision_poly_size; + + // point-point + if (one->collision_poly_size == 1 && two->collision_poly_size == 1) { + if (point_point_colcheck(one->collision_poly[0], two->collision_poly[0])) { + return true; + } + } + // point-line + if ((onesize == 1 || twosize == 1) && (onesize == 2 || twosize == 2)) { + Vect line[2]; + Vect point; + if (onesize > twosize) { + line[0] = one->collision_poly[0]; + line[1] = one->collision_poly[1]; + point = two->collision_poly[0]; + } else { + line[0] = two->collision_poly[0]; + line[1] = two->collision_poly[1]; + point = one->collision_poly[0]; + } + + return point_line_colcheck(line, point); + } + + // line-line + if ((onesize == 2 && twosize == 2)) { + return false; + } + + // point-poly + if ((onesize == 1 || twosize == 1) && (onesize > 2 || twosize > 2)) { + return false; + } + + // line-poly + if ((onesize == 2 || twosize == 2) && (onesize > 2 || twosize > 2)) { + return false; + } + + // poly-poly + if ((onesize > 2 && twosize > 2)) { + return sat_collision_check(one, two, translation); + + } +} + +void destroy_physics_body(Body *b) { + // collisions + Vect *collision_poly; + Vect *collision_shape; + int collision_poly_size; + int collision_shape_size; + void (*updateCollisionPoly)(struct BodyStruct *); + // applying forces + int num_strings; + int max_strings; + String *strings; + +} + +void destroy_physics_collection(struct physics_collection *s) { + for (int i = 0; i < s->numItems; i++) { + if (s->items[i]) { + //free(s->items[i]); + } + } + + free(s->items); +} + + + +void next_level(int lvl) { + level = lvl; + load_level(); +} + +void load_level() { + SDL_LockMutex(player.physics->lock); + Vect v; + v.x = 0; + v.y = 0; + player.physics->vel = v; + player.physics->acc = v; + stop_pull_rope(); + player.physics->strings[0].attached = false; + + int d = -1; + for (int i = 0; i < world.items.size; i++) { + if (world.get(i).kind == ROOM_W) { + d = i; + break; + } + } + + if (d != -1) { + world_thing *w = arlst_del(&world.items, d); + } + + destroy_physics_collection(&world.uniques_index[ROOM_W]->room->ceil); + destroy_physics_collection(&world.uniques_index[ROOM_W]->room->floor); + //destroy_environment(&world.uniques_index[ROOM_W]->room->env); + + get_floor_ceiling(); +#ifdef SCORE_SYSTEM + draw_watch.best_time = get_best_time(); + load_score_times("saves/times"); +#endif + + v = world.uniques_index[ROOM_W]->room->ceil.items[2]->collision_poly[0]; + v.y -= 15; + + player.physics->position = v; + player.physics->next_position = v; + + quality = 100; + level_timer_update(true); + draw_watch.finish_level = false; + + viewport_pos.x = player.physics->position.x - width / 2; + viewport_pos.y = player.physics->position.y - height / 2; + + SDL_UnlockMutex(player.physics->lock); + + Mix_HaltChannel(-1); + + // reset physics + + struct timespec now = get_now(); + double time_delta = now.tv_nsec - last_tick.tv_nsec; + if (now.tv_nsec < last_tick.tv_nsec) { + time_delta = now.tv_nsec - (last_tick.tv_nsec - 1000000000); + } + last_tick = now; + + + +} + +int get_floor_ceiling() { + struct environment e = get_scene_at(viewport_pos, level); + struct physics_collection floor; + struct physics_collection ceil; + + floor.items = calloc(e.floor.size - 1, sizeof(Body*)); + ceil.items = calloc(e.ceil.size - 1, sizeof(Body*)); + + floor.numItems = e.floor.size - 1; + ceil.numItems = e.ceil.size - 1; + + for (int i = 0; i < e.floor.size-1; i++) { + get_new_physics(&floor.items[i]); + get_new_physics(&ceil.items[i]); + Body *fseg = floor.items[i]; + Body *cseg = ceil.items[i]; + + fseg->position = *(Vect *)arlst_get(&e.floor, i); + fseg->collision_poly_size = 4; + fseg->collision_poly = calloc(4, sizeof(Vect)); + fseg->collision_shape = calloc(4, sizeof(Vect)); + + cseg->position = *(Vect *)arlst_get(&e.ceil, i); + cseg->collision_poly_size = 4; + cseg->collision_poly = calloc(4, sizeof(Vect)); + cseg->collision_shape = calloc(4, sizeof(Vect)); + + Vect fthis = fseg->position; + Vect cthis = cseg->position; + + Vect fnext = *(Vect *)arlst_get(&e.floor, i + 1); + Vect cnext= *(Vect *)arlst_get(&e.ceil, i + 1); + + double frise = fnext.y - fthis.y; + double frun = fnext.x - fthis.x; + + double crise = cnext.y - cthis.y; + double crun = cnext.x - cthis.x; + + double fdepth = -(fabs(frise) * 2 + 100); + double cdepth = fabs(crise) * 2 + 100; + + fseg->collision_shape[0].x = 0; + fseg->collision_shape[0].y = 0; + + fseg->collision_shape[1].x = frun; + fseg->collision_shape[1].y = frise; + + fseg->collision_shape[2].x = frun; + fseg->collision_shape[2].y = frise + fdepth; + + fseg->collision_shape[3].x = 0; + fseg->collision_shape[3].y = frise + fdepth; + + cseg->collision_shape[0].x = 0; + cseg->collision_shape[0].y = 0; + + cseg->collision_shape[1].x = crun; + cseg->collision_shape[1].y = crise; + + cseg->collision_shape[2].x = crun; + cseg->collision_shape[2].y = crise + cdepth; + + cseg->collision_shape[3].x = 0; + cseg->collision_shape[3].y = crise + cdepth; + + default_update_collision_poly(cseg); + default_update_collision_poly(fseg); + } + + world_thing room_world; + room_world.kind = ROOM_W; + + struct room* room = malloc(sizeof(struct room)); + room->ceil = ceil; + room->floor = floor; + room_world.room = room; + + add_to_world(room_world); + world.uniques_index[ROOM_W]->room->env = e; + return 0; +} + +Wall *get_long_wall(int numNodes, int *nodes) { + Wall wall; + memset(&wall, 0, sizeof(wall)); + + wall.numNodes = numNodes; + wall.nodes = calloc(numNodes, sizeof(SDL_Point)); + get_new_physics(&wall.physics); + + wall.physics->collision_poly = calloc(numNodes, sizeof(Vect)); + wall.physics->collision_shape = calloc(numNodes, sizeof(Vect)); + wall.physics->collision_poly_size = numNodes; + + // collisions + //SDL_Point *collision_poly; + for (int i = 0; i < numNodes; i++) { + wall.nodes[i].x = nodes[2*i]; + wall.nodes[i].y = nodes[2*i+1]; + + wall.physics->collision_shape[i].x = nodes[2*i]; + wall.physics->collision_shape[i].y = nodes[2*i+1]; + } + + Wall *wallwall = malloc(sizeof(wall)); + *wallwall = wall; + return wallwall; +} + +void accel_thing(Body * thing, double x, double y) { + /* takes acceleration in m/s2 and converts to m/ms adding + * it to physics_thing + */ + + // convert to m / millisecond + double x_adj = x / 1000.0; + double y_adj = y / 1000.0; + + thing->acc.y += y_adj; + thing->acc.x += x_adj; + +} + +void set_motor_max_velocity(Body *thing, int motorID, double max) { + thing->motors[motorID].max_velocity = max; +} + +void set_motor_timeout(Body *thing, int motorID, uint32_t timeout) { + // this don't work yo + clock_gettime(CLOCK_MONOTONIC, &thing->motors[motorID].timeout); + thing->motors[motorID].timeout.tv_nsec += 1000000 * timeout; + thing->motors[motorID].timeout.tv_sec + = thing->motors[motorID].timeout.tv_nsec * 0.000000001; +} + +void set_motor_status(Body *thing, int motorID, bool run) { + if (motorID == M_GRAVITY && run == false) { + *(int *)0 = 1; + } + thing->motors[motorID].stop = !run; + +} + +void set_motor_newtons(Body *thing, int motorID, double x, double y) { + thing->motors[motorID].x =x; + thing->motors[motorID].y =y; +} + +void add_motor_newtons(Body *thing, int motorID, double x, double y) { + thing->motors[motorID].x +=x; + thing->motors[motorID].y +=y; +} + +// @param thing: the body to apply the motor to +// @param x, y: The initial motor force vector. +void add_motor(Body *thing, double x, double y) { + Motor motor; + memset(&motor, 0, sizeof(Motor)); + motor.x = x; + motor.y = y; + motor.stop = true; + + motor.timeout.tv_sec = 0; + motor.timeout.tv_nsec = 0; + + motor.max_velocity = 999899; + + motor.update_motor = default_motor_curve; + + motor.end_object = thing; + + if (thing->num_motors == thing->max_motors) { + thing->motors = realloc(thing->motors, sizeof(Motor) * (thing->max_motors *=2)); + } + + thing->motors[thing->num_motors] = motor; + thing->num_motors += 1; +} + +void ratcheted_winch_motor_update(Motor* motor) { + + Body *body = motor->end_object; + + if (body->strings[0].max_length < 0.1 + || !body->strings[0].attached || motor->stop) { + motor->stop = true; + return; + } + + Vect st; + st.x = body->position.x - body->strings[0].end_point.x; + st.y = body->position.y - body->strings[0].end_point.y; + + if (body->strings[0].max_length > vect_mag(st)) + body->strings[0].max_length = vect_mag(st); + winch_motor_update(motor); + + play_game_sound(game_sounds.rope_pull, 300, BC_ROPE_PULL); + +} + +void winch_motor_update (Motor* motor) { + Vect v; + v.x = motor->x; + v.y = motor->y; + double mod = vect_mag(v); + + Body *body = motor->end_object; + + if (body->strings[0].max_length < 0.1 + || !body->strings[0].attached) { + motor->stop = true; + return; + } + + // set the motor direction to the string direction + Vect end_point = body->strings[0].end_point; + Vect start_point = body->position; + Vect dir = vect_add(end_point, vect_scalar(start_point, -1)); + + double arg = vect_arg(dir); + Vect force = vect_modarg(mod, arg); + + motor->x = force.x; + motor->y = force.y; +} + +void pull_rope(double newtons) { + if (!player.physics->strings[0].attached) { + return; + } + if (!player.physics->motors[M_WINCH].stop) { + return; + } + + // set force + set_motor_newtons(player.physics, M_WINCH, 0, newtons); + set_motor_status(player.physics, M_WINCH, true); + // point it in the right direction + winch_motor_update(player.physics->motors + M_WINCH); + player.physics->motors[M_WINCH].stop = false; +} + +void stop_pull_rope(void) { + Mix_HaltChannel(BC_ROPE_PULL); + player.physics->motors[M_WINCH].stop = true; +} + + +void add_rope(int mouse_x, int mouse_y) { + if (player.physics->strings[0].attached) { + return; + } + if (game_paused || !in_game) { + return; + } + + Vect mouse; + mouse.x = mouse_x; + mouse.y = mouse_y; + mouse = vect_add(mouse, viewport_pos); + + Vect start = player.physics->position; + + Vect end; + end.x = 0; + end.y = 0; + + struct room *room = world.uniques_index[ROOM_W]->room; + struct physics_collection floor = room->floor; + struct physics_collection ceil = room->ceil; + + Vect ray_e = player.physics->position; + Vect ray_s = mouse; + + double x1 = ray_s.x; + double y1 = ray_s.y; + + double x2 = ray_e.x; + double y2 = ray_e.y; + + int i; + struct physics_collection s; + bool found = false; + double len = MAX_ROPE_GRAB_LEN; + double t, u; + for (int j = 0; j < (floor.numItems + ceil.numItems - 2); j++) { + if (j >= floor.numItems - 1) { + s = ceil; + i = j - floor.numItems + 1; + } else { + s = floor; + i = j; + } + + Vect wall_s = s.items[i]->position; + Vect wall_e = s.items[i+1]->position; + + Vect intersect; + + double x3 = wall_s.x; + double y3 = wall_s.y; + + double x4 = wall_e.x; + double y4 = wall_e.y; + + t = ((x1 - x3)*(y3 - y4) - (y1 - y3)*(x3-x4)) + / ((x1-x2)*(y3-y4) - (y1 - y2)*(x3-x4)); + + u = ((x1 - x2)*(y1 - y3) - (y1 - y2)*(x1 - x3)) + / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); + + if (t < 0 || u > 0 || t > 1 || u < -1) { + continue; + } else { + intersect.x = ((x1*y2 - y1*x2)*(x3 - x4) -(x1 - x2)*(x3*y4 - y3*x4)) + / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); + + intersect.y = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2) * (x3*y4 - y3 * x4)) + / ((x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)); + + Vect rope = vect_add(intersect, vect_scalar(start, -1)); + double mag = vect_mag(rope); + if (mag < len) { + len = mag; + found = true; + end = intersect; + } + } + } + + if (found) { + + SDL_SetRenderDrawColor(debug_ren, 200, 255, 30, 255); + SDL_RenderDrawLine(debug_ren, start.x - viewport_pos.x,start.y-viewport_pos.y, + end.x - viewport_pos.x, end.y -viewport_pos.y); + + Vect rop = vect_add(end, vect_scalar(start, -1)); + + player.physics->strings[0].max_length = vect_mag(rop); + + player.physics->strings[0].set_end_point(player.physics->strings, &end); + player.physics->strings[0].attached = true; + play_game_sound(game_sounds.rope_attach, 100, BC_ROPE_ATTACH); + + } +} + +void delete_rope(void) { + player.physics->strings[0].attached = false; +} + +// basic collision handler for testing +// +int process_collisions(Body *thing, Vect *trans) { + Vect translation; + translation.x = translation.y = 0; + Vect temptrans; + temptrans.x = temptrans.y = 0; + int num_cols = 0; + int num = 0; + + thing->was_colliding += thing->colliding > 0 ? 1 : -1; + if (thing->was_colliding < -1000) { + thing->was_colliding = -1000; + } + if (thing->was_colliding > 1000) { + thing->was_colliding = 1000; + } + + for (int k = 0; k < world.items.size; k++) { + if (world.get(k).kind == STATIC_WALL_W + && world.get(k).wall->physics->uid != thing->uid) { + if ((world.get(k).wall->physics->position.x - viewport_pos.x) > (width * 2) + || world.get(k).wall->physics->position.x - viewport_pos.x < 0) { + continue; + } else { + num++; + } + if (check_collision(world.get(k).wall->physics, thing, &temptrans)) { + num++; + thing->colliding = true; + translation = vect_add(translation, temptrans); + } + } else if (world.get(k).kind == FLOOR || world.get(k).kind == CEILING) { + for (int i = 0; i < world.get(k).floor->numPolys; i++) { + if ((world.get(k).floor->polys[i].physics->position.x - viewport_pos.x) > (2 *width) + || (world.get(k).floor->polys[i].physics->position.x - viewport_pos.x) < -width) { + continue; + } else { + num++; + } + if (check_collision(world.get(k).floor->polys[i].physics, thing, &temptrans)) { + num_cols++; + thing->colliding = true; + translation = vect_add(translation, temptrans); + } + } + } else if (world.get(k).kind == ROOM_W) { + for (int i = 0; i < world.get(k).room->floor.numItems; i++) { + Body *s = world.get(k).room->floor.items[i]; + if (s->position.x + E_ROOM_RES < viewport_pos.x) { + continue; + } else if (s->position.x > viewport_pos.x + width) { + continue; + } else { + num++; + } + + if (check_collision(s, thing, &temptrans)) { + num_cols++; + thing->colliding = true; + translation = vect_add(translation, temptrans); + } + } + + for (int i = 0; i < world.get(k).room->ceil.numItems; i++) { + Body *s = world.get(k).room->ceil.items[i]; + if (s->position.x + E_ROOM_RES < viewport_pos.x) { + continue; + } else if (s->position.x > viewport_pos.x + width) { + continue; + } else { + num++; + } + + if (check_collision(s, thing, &temptrans)) { + num_cols++; + thing->colliding = true; + translation = vect_add(translation, temptrans); + } + } + } + } + + + if (!num_cols) { + thing->colliding = false; + return false; + } else { + *trans = translation; + return true; + } +} + +struct timespec get_now() { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (now.tv_nsec >= 1000000000) { + now.tv_nsec -= 1000000000; + } + return now; +} + +bool get_motor_active(Body *thing, int i) { + if (thing->motors[i].stop) { + return false; + } + + struct timespec now = get_now(); + if (thing->motors[i].timeout.tv_nsec + || thing->motors[i].timeout.tv_sec) { + // 0 is a sentinel for infinity + if (thing->motors[i].timeout.tv_sec > now.tv_sec + && thing->motors[i].timeout.tv_nsec > now.tv_nsec) { + return false; + } + } + + return true; +} + +/* Basic physics works by adding up the acceleratino caused by all the forces on + * the object, converting it to velocity, then doing collision detection and + * applying various reactive forces (friction etc) and finally adjusting the + * object's position. + */ +void advance_thing(Body * thing) { + // TODO: fix ordering of collision detection + physics sim so that collisions + // are less bad. + + thing->acc.x = 0; + thing->acc.y = 0; + + if (!thing->dynamics) { + return; + } + + double time_delta = TIMESTEP_LENGTH; + + Vect translation; + Vect friction; + translation.x = translation.y = 0; + int numcols = 0; + + // collisions + if ((numcols = process_collisions(thing, &translation))) { + /*double check = vect_scalar_projection(translation, thing->vel);*/ + /*if (check >= 0) {*/ + /*translation.x *= -1;*/ + /*translation.y *= -1;*/ + /*}*/ + + // correct position using translation vector. + thing->next_position.x += translation.x; + thing->next_position.y += translation.y; + + // make unit vector + double mag = vect_mag(translation); + translation.x = translation.x / mag; + translation.y = translation.y / mag; + + if (mag > 0.10) { + Mix_Volume(BC_COLLISION, MIX_MAX_VOLUME * mag); + play_game_sound(game_sounds.collision, 230, BC_COLLISION); + } + + // get velocity in direction of collision + mag = vect_scalar_projection(thing->vel, translation); + + Vect revert_vel; + + revert_vel.x = cos(vect_dir(translation)) * mag; + revert_vel.y = sin(vect_dir(translation)) * mag; + + thing->vel.x -= revert_vel.x; + thing->vel.y -= revert_vel.y; + + // add elasticity + thing->vel.x -= revert_vel.x * sqrt(thing->obj_elasticity); + thing->vel.y -= revert_vel.y * sqrt(thing->obj_elasticity); + + // add friction + + if (vect_mag(translation) < 0.1) { + Vect friction = vect_rotate(revert_vel, M_PI / 2); + double dir = vect_scalar_projection(thing->vel, friction); + if (dir > 0) { + friction = vect_rotate(friction, M_PI); + } + + if (fabs(thing->vel.x) <= fabs(friction.x)) { + thing->vel.x = 0; + } else { + thing->vel.x += thing->obj_friction * friction.x; + } + if (fabs(thing->vel.y) <= fabs(friction.y)) { + thing->vel.y = 0; + } else { + thing->vel.y += thing->obj_friction * friction.y; + } + + } + + /*double norm = 9.81 * thing->obj_mass;*/ + /*Vect x;*/ + /*x.y = 0; x.x = 1;*/ + /*if (vect_scalar_projection(thing->vel, translation) > 0) {*/ + /*friction.x = translation.y;*/ + /*friction.y = -translation.x;*/ + /*} else {*/ + /*friction.x = -translation.y;*/ + /*friction.y = translation.x;*/ + /*}*/ + + // force + /*friction.x = friction.x * norm * fric_const;*/ + /*friction.y = friction.y * norm * fric_const;*/ + // printf("friction accel: %e %e\n", friction.x, friction.y); + // printf("velocity: %e %e\n", thing->vel.x, thing->vel.y); + + /*set_motor_newtons(thing, M_FRICTION, friction.x, friction.y);*/ + /*set_motor_max_velocity(thing, M_FRICTION, vect_mag(project_vect(thing->vel, friction)));*/ + + // restitution force + /*rest.x = 0;*/ + /*rest.y = 0;*/ + /*double impulse = thing->obj_mass * vect_mag(thing->vel) * thing->obj_elasticity;*/ + /*rest.x = cos(vect_dir(translation)) * impulse;*/ + /*rest.y = sin(vect_dir(translation)) * impulse;*/ + /*accel_thing(thing, rest.x, rest.y);*/ + } + + // pendulums + for (int i = 0; i < thing->num_strings; i++) { + if (!thing->strings[i].attached) { + continue; + } + + double st_len = vect_distance(thing->next_position, thing->strings[i].end_point); + double max_len = thing->strings[i].max_length; + if (st_len > max_len) { + Vect string_end = thing->strings[i].end_point; + Vect thing_position = thing->next_position; + + Vect string; + string.x = string_end.x - thing_position.x; + string.y = string_end.y - thing_position.y; + double mag = vect_mag(string); + string.x /= mag; + string.y /= mag; + double angle = atan2(string.y, string.x); + double disp = mag - max_len; + + thing->next_position.x += cos(angle) * disp; + thing->next_position.y += sin(angle) * disp; + + // set velocity to 0 in direction of string + double corr_mag = vect_scalar_projection(thing->vel, string); + + thing->vel.x -= corr_mag * cos(angle); + thing->vel.y -= corr_mag * sin(angle); + } + } + + // motors + for (int i = 0; i < thing->num_motors; i++) { + + if (!get_motor_active(thing, i)) { + continue; + } + + // dynamic motor curve + if (thing->motors[i].update_motor) { + thing->motors[i].update_motor(thing->motors + i); + } + Vect F; + Vect V; + F.x = thing->motors[i].x; + F.y = thing->motors[i].y; + V.x = thing->vel.x; + V.y = thing->vel.y; + + Vect vel_in_dir = project_vect(V, F); + double dirF = atan2(F.y, F.x); + double dirV = atan2(V.y, V.x); + + double diff = dirV > dirF ? dirV - dirF: dirF - dirV; + + if (thing->motors[i].max_velocity > vect_mag(vel_in_dir) + || diff >= M_PI) { + double acc_x = thing->motors[i].x / thing->obj_mass; + double acc_y = thing->motors[i].y / thing->obj_mass; + accel_thing(thing, acc_x, acc_y); + } + } + + // accelerate based on accel + thing->vel.x += thing->acc.x * (double)time_delta; + thing->vel.y += thing->acc.y * (double)time_delta; + + double velocity = sqrt((double)(thing->vel.x * thing->vel.x + thing->vel.y * thing->vel.y)); + + // simple air drag + + /*if (velocity > 0.000000000000000) {*/ + /*double dir = atan2((double)thing->y_vel, (double)thing->x_vel) + M_PI;*/ + + /*double absolute_force = 5 * thing->obj_mass * (velocity / time_delta); // 2 * 0.1231 * 5;*/ + /*printf("dir %e %e\n\n", dir, dir - M_PI);*/ + /*printf("force %e\n\n", absolute_force);*/ + + /*double f_x = (cos(dir)) * absolute_force;*/ + /*double f_y = (sin(dir)) * absolute_force;*/ + + /*accel_thing(thing, (float)f_x, (float)f_y);*/ + + /*}*/ + + if (fabsl((*thing).vel.x) < 0.001) { + (*thing).vel.x = 0; + } + if (fabsl((*thing).vel.y) < 0.001) { + (*thing).vel.y = 0; + } + + if (thing->lock) { + SDL_LockMutex(thing->lock); + } + thing->position = thing->next_position; + + double oldx = thing->position.x; + double oldy = thing->position.y; + thing->next_position.x += (thing->vel.x * 1/2 * (double)time_delta); + thing->next_position.y += (thing->vel.y * 1/2 * (double)time_delta); + + + if (!thing->collision_poly[0].y) { + thing->updateCollisionPoly(thing); + } + + thing->updateCollisionPoly(thing); + if (thing->lock) { + SDL_UnlockMutex(thing->lock); + } + +} + +/*void destroy_projectile(Projectile** proj) {*/ + /*for (int i = 0; i < things_in_world; i ++) {*/ + /*if (world[i].projectile == *proj) {*/ + + /*}*/ + /*}*/ +/*}*/ + +void *default_projectile_step(struct Projectile *self) { + advance_thing(self->physics); + return NULL; +} + +void *default_projectile_on_collision(struct Projectile *self) { + self->physics->dynamics = false; + return NULL; +} + +void get_projectile(Projectile** proj) { + *proj = calloc(1, sizeof(Projectile)); + get_new_physics(&(*proj)->physics); + (*proj)->physics->dynamics = true; + (*proj)->on_step = default_projectile_step; + (*proj)->on_collision = default_projectile_on_collision; +} + +void advance_things(void) { + int numcols; + Vect translation; + + /* update the timer */ + struct timespec now = get_now(); + double time_delta = now.tv_nsec - last_tick.tv_nsec; + + if (now.tv_nsec < last_tick.tv_nsec) { + time_delta = now.tv_nsec - (last_tick.tv_nsec - 1000000000); + } + time_delta *= 0.000001; // convert to ms from ns + time_remaining += time_delta; + last_tick = now; + + while (time_remaining > TIMESTEP_LENGTH) { + time_remaining -= TIMESTEP_LENGTH; + for (int i = 0; i < world.items.size; i++) { + switch (world.get(i).kind) { + case PLAYER_W : + advance_thing((world.get(i).player->physics)); + continue; + case STATIC_WALL_W: + advance_thing(world.get(i).wall->physics); + continue; + case FLOOR: + continue; + /*for (int k = 0; k < world.get(i).floor->numPolys; k++) {*/ + /*advance_thing(world.get(i).floor->polys[k].physics);*/ + /*}*/ + /*break;*/ + case CEILING: + continue; + /*for (int k = 0; k < world.get(i).floor->numPolys; k++) {*/ + /*advance_thing(world.get(i).floor->polys[k].physics);*/ + /*}*/ + /*break;*/ + case PROJECTILE: + if ((numcols = process_collisions(world.get(i).projectile->physics, + &translation))) { + world.get(i).projectile->on_collision(world.get(i).projectile); + } + world.get(i).projectile->on_step(world.get(i).projectile); + continue; + default: + continue; + } + } + } +} + +bool unique_world_kind(enum world_thing_kind k) { + switch (k) { + case PLAYER_W: + case FLOOR: + case CEILING: + case ROOM_W: + return true; + default: + return false; + } +} + +// grow array of world things if needed +void add_to_world(world_thing thing) { + thing.nid = world.items.size; + world_thing *t = calloc(1, sizeof(world_thing)); + memcpy(t, &thing, sizeof(world_thing)); + + arlst_add(&world.items, t); + + if (unique_world_kind(thing.kind)) { + void *ref = arlst_get(&world.items, world.items.size - 1); + world.uniques_index[thing.kind] = ref; + } + +} + +/* Send a projectile from body in direction dir (in degrees) with the force of + * sten newtons */ +void fire_projectile(Body *from, int stren, int dir) { + Projectile *proj; + get_projectile(&proj); + + double radians = (double)dir * (M_PI / 180); + set_motor_newtons(proj->physics, 0, stren * cos(radians), + stren * sin(radians)); + set_motor_timeout(proj->physics, 0, 2); + + world_thing proj_world; + proj_world.projectile = proj; + proj_world.kind = PROJECTILE; + add_to_world(proj_world); +} + +void get_room(void) { + int floorsize = 100; + FloorPoly *polys = generate_floor_simple(floorsize, true, 1300); + Floor *floor = calloc(1, sizeof(Floor)); + floor->polys = polys; + floor->numPolys = floorsize; + + FloorPoly *ceil = generate_floor_simple(floorsize, false, 1000); + +// printf("floor: %f %f\n", polys[2].left.x, polys[2].left.y); +// printf("ceil: %f %f\n", ceil[2].left.x, ceil[2].left.y); + + Floor* ceiling = calloc(1, sizeof(Floor)); + ceiling->polys = ceil; + ceiling->numPolys = floorsize; + + world_thing ceilingthing; + ceilingthing.kind = CEILING; + ceilingthing.floor = ceiling; + add_to_world(ceilingthing); + + world_thing floorthing; + floorthing.kind = FLOOR; + floorthing.floor = floor; + add_to_world(floorthing); +} + +GlobWorld create_world() { + GlobWorld c; + c.items = new_arlst(100); + c.uniques_index = calloc(10, sizeof(world_thing*)); + c.get = world_getter; // gross + return c; +} + +void startgame(SDL_Renderer * ren) { + get_input_map(); + + textboxes[TB_LEVEL_CHOOSER].id = TB_LEVEL_CHOOSER; + textboxes[TB_LEVEL_CHOOSER].close_callback = new_level_tb_close_callback; + + debug_ren = ren; + + level = 1; + +#ifdef SCORE_SYSTEM + save_times_lst = (struct sg_times_list){}; + load_score_times("saves/times"); +#endif + + world = create_world(); + + SDL_GetRendererOutputSize(ren, &width, &height); + +// player = get_player(200,1300); + + get_floor_ceiling(); + Vect stpos = *world.uniques_index[ROOM_W]->room->ceil.items[2]->collision_poly; + player = get_player(stpos.x, stpos.y - 15); + world_thing player_world; + + player_world.kind = PLAYER_W; + player_world.player = malloc(sizeof(player)); + *player_world.player = player; + glob_player = player_world.player; + + add_to_world(player_world); + + viewport_pos.x = 700; + viewport_pos.y = 0; + + level_timer_update(true); + game_paused = 0; + viewport_pos.x = player.physics->position.x - width / 2; + viewport_pos.y = player.physics->position.y - height / 2; + + //get_room(); +} + +double get_abs(double x,double y) { + return (sqrt(x*x + y*y)); +} + +void walk_player(int x, int y) { + + set_motor_status(player.physics, M_PLAYER_WALK, true); + set_motor_newtons(player.physics, M_PLAYER_WALK, 0, 0); + // turned off special case for going down for TESTing + if (false && y == -1) { + add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 0, 100 * 10 * y); + return; + } + add_motor_newtons(glob_player->physics, M_PLAYER_WALK, 100 *5* x , 100 * 5 * y); +} + +void handle_input_event(SDL_Event event) { + SDL_Scancode sc = event.key.keysym.scancode; + static bool mouse_down = false; + switch (event.type) { + case SDL_KEYDOWN: + + if (gameui.currently_bound_textbox) { + if (event.key.keysym.scancode == SDL_SCANCODE_BACKSPACE + || event.key.keysym.scancode == SDL_SCANCODE_DELETE) { + delete_from_textbox(gameui.currently_bound_textbox); + } else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN + || event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) { + if (gameui.currently_bound_textbox->close_callback) { + gameui.currently_bound_textbox->close_callback( + gameui.currently_bound_textbox, NULL); + } + gameui.currently_bound_textbox = NULL; + } + else if (event.key.keysym.sym >= 32 + && event.key.keysym.sym <= 126) { + write_to_textbox(gameui.currently_bound_textbox, + (char)event.key.keysym.sym); + } + // dont do anything else when the textbox is bound; + break; + } + + +#ifdef DEBUGMODE + if (sc == input_map.player_up) { + walk_player(0, -1); + } if ( sc == input_map.player_left) { + walk_player(-1, 0); + } if (sc == input_map.player_down) { + walk_player(0, 1); + } if (sc == input_map.player_right) { + walk_player(1, 0); + } +#endif + if (sc == input_map.player_pull_rope) { + pull_rope(900); + } if (sc == input_map.mute) { + mute = !mute; + } if (sc == input_map.pause) { + if (in_game) + game_paused = !game_paused; + } + + if (sc == input_map.goto_level) { + gameui.currently_bound_textbox = textboxes + TB_LEVEL_CHOOSER; + }; + + + break; + case SDL_KEYUP: + if (event.key.keysym.scancode == input_map.player_rope) { + delete_rope(); + } +#ifdef DEBUGMODE + if (sc == input_map.player_up || sc == input_map.player_down + || sc == input_map.player_left + || sc == input_map.player_right) { + set_motor_newtons(player.physics, M_PLAYER_WALK, 0, 0); + set_motor_status(player.physics, M_PLAYER_WALK, false); + } + if (event.key.keysym.scancode == SDL_SCANCODE_F10) { + next_level(); + } +#endif + if (sc == input_map.player_pull_rope) { + stop_pull_rope(); + } + + break; + case SDL_MOUSEBUTTONDOWN: + add_rope(event.button.x, event.button.y); + if (event.button.button == input_map.mouse_attach_rope) + mouse_down = true; + if (event.button.button == input_map.mouse_attach_rope_pull) + pull_rope(900); + break; + case SDL_MOUSEBUTTONUP: + if (event.button.button == input_map.mouse_attach_rope) { + mouse_down = false; + delete_rope(); + } + if (event.button.button == input_map.mouse_attach_rope_pull) { + stop_pull_rope(); + if (!mouse_down) { + delete_rope(); + } + } + } +} + + +/* temporary storage variable *n should be initialised to 1 */ +int get_rand(int *n) { + const unsigned c = 11; + const unsigned m = (1 << 31) - 1; + const unsigned initial_n = 1; + const unsigned a = 48271; + + *n = (a * *n + c) % m; + return *n; +} + + +int step(void) { + static int first_run = 0; + if (!first_run) { + start_audio(); +// play_game_sound(game_sounds.background, -1, BC_MUSIC); + first_run = 1; + game_paused = 0; + } + + if (gameui.goto_new_level) { + int nevl = gameui.goto_new_level; + gameui.goto_new_level = 0; + return nevl; + } + + if (player.physics->position.x > world.uniques_index[ROOM_W]->room + ->floor.items[world.uniques_index[ROOM_W]->room->floor.numItems - 1]->position.x) { + draw_watch.finish_level = true; + return level + 1; + } + + if (!game_paused) { + if (player.physics->position.x > world.uniques_index[ROOM_W]->room + ->floor.items[3]->position.x) { + level_timer_update(false); + } + + if (in_game) { + advance_things(); + } + } else { + last_tick = get_now(); + } + + return 0; +} + diff --git a/build-android/src/game.h b/build-android/src/game.h new file mode 100644 index 0000000..405d935 --- /dev/null +++ b/build-android/src/game.h @@ -0,0 +1,82 @@ +#ifndef _DEFGAME +#define _DEFGAME + +#include "environment.h" +#include "datatypes.h" +#include "vect.h" +#include "controlscheme.h" +#include "colours.h" +#include "types.h" +#include "draw.h" + +extern GlobWorld world; +extern int level; +extern bool in_game; +extern int quality; + +struct draw_watcher { + long best_time; + bool finish_level; +}; + + +struct ui_state { + struct textbox_info *currently_bound_textbox; + int goto_new_level; +}; + + +enum TextBoxId { + TB_LEVEL_CHOOSER = 0 +}; + +struct textbox_info { + enum TextBoxId id; + bool capture_input_text_field; + char text_input[32]; + int text_input_bufpos; + void (*close_callback)(struct textbox_info *textbox, void*callback); +}; + +extern struct draw_watcher draw_watch; +extern struct textbox_info* texboxes; +extern struct ui_state gameui; + + +void handle_input_event(SDL_Event event); + +// add a motor to the world +void add_motor(Body *thing, double x, double y); + +bool get_motor_active(Body *thing, int i); + +/* temporary storage variable *n should be initialised to the seed value */ +int get_rand(int *n); + +void next_level(int lvl); + +int step(void); + +struct sg_times_list { + long *times; + int size; + int capacity; + void (*sort)(void); + char *filename; +}; + +int load_score_times(char *filename); + + +/* array of all the things in the world and their kinds */ + +extern struct sg_times_list save_times_lst; +int add_time(long time); +int write_times(void); +extern void startgame(SDL_Renderer * ren) ; +extern void process_keydown(SDL_Keysym key); +extern void process_keyup(SDL_Keysym key); +extern player_st player; +extern long level_time; +extern bool game_paused; +#endif diff --git a/build-android/src/main.c b/build-android/src/main.c new file mode 100644 index 0000000..f56c5fc --- /dev/null +++ b/build-android/src/main.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio.h" // only for mkdirat lol +#include +#include + +#include "game.h" +#include "draw.h" +#include "types.h" + +const int screen_width = 800; +const int screen_height = 600; + + +SDL_sem *resume; + + +struct SDL_Window* make_window(void) { + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { + printf("error initializing SDL: %s\n", SDL_GetError()); + } + + + return SDL_CreateWindow("space_game", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 0, 0, + SDL_WINDOW_FULLSCREEN_DESKTOP); +} + + +void redraw(struct SDL_Renderer * ren) { + SDL_RenderClear(ren); + redraw_buffer(ren); + SDL_RenderPresent(ren); +} + + +void godophysics(void) { + int lvl; + if ((lvl = step())) { + // display end level screen + in_game = false; + game_paused = true; + SDL_Delay(300); + SDL_SemWait(resume); +#ifdef SCORE_SYSTEM + add_time(level_time); +#endif + + SDL_LockMutex(player.physics->lock); + next_level(lvl); + in_game = true; + SDL_UnlockMutex(player.physics->lock); + game_paused = false; + } +} + +int physics_loop(void *ptr) { + game_paused = 1; + while (1) { + godophysics(); + } +} + + + +int game(void) { + //SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "2" ); + + SDL_Window * win = make_window(); + SDL_Renderer * ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); +// | SDL_RENDERER_PRESENTVSYNC); + SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND); + + + in_game = true; + resume = SDL_CreateSemaphore(0); + + // IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG); + + if (ren == NULL) { + SDL_DestroyWindow(win); + SDL_Quit(); + } + + TTF_Init(); + + int close = 0; + + //draw_pictures(ren); + + SDL_Thread *physics_thread; + int ignore; + + startgame(ren); + + physics_thread = SDL_CreateThread(physics_loop, "physics", (void *)ren); + + int count = 0; + + while (!close) { + SDL_Event event; + while(SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + goto endfunc; + case SDL_KEYDOWN: + if (event.key.keysym.scancode == SDL_SCANCODE_Q) { + goto endfunc; + } + if (!in_game && !SDL_SemValue(resume)) { + SDL_SemPost(resume); + } + case SDL_KEYUP: + case SDL_MOUSEBUTTONDOWN: + if (!in_game && !SDL_SemValue(resume)) { + SDL_SemPost(resume); + break; + } + case SDL_MOUSEBUTTONUP: + handle_input_event (event); + } + } + + if (!in_game && draw_watch.finish_level) { + draw_end_screen(ren); + } else { + redraw(ren); + } + } + +endfunc: + + SDL_DestroyRenderer(ren); + SDL_DestroyWindow(win); + return 0; +} + + +int main (int argc, char** argv) { + +#ifdef __linux__ + mkdirat(AT_FDCWD, "saves", 0777); +#elif WIN32 + if (access("saves", 0) == 0) { + struct stat status; + stat("saves", &status ); + if ((status.st_mode & S_IFDIR) == 0) { + _mkdir("saves"); + } + } +#endif + + game(); + + SDL_Quit(); + return 0; +} + + + +/* + * TODO: + * - Allow setting and saving/loading differnet control schemes + * - Allow starting a specific level + * - Allow adjusging level lengths? + * - Ensure next_level doesn't leak memory + * - fix that weird jitter + * - make the end of level look sane + * - restart level + * - make sure goto level doesn't log an end-0f-level time + */ + diff --git a/build-android/src/main.cpp b/build-android/src/main.cpp new file mode 100644 index 0000000..c6fca0c --- /dev/null +++ b/build-android/src/main.cpp @@ -0,0 +1,61 @@ +#include +#include + +int main(int /*argc*/, char* /*argv*/[]) { + + SDL_Window *window; // Declare a pointer + + SDL_Init(SDL_INIT_VIDEO); // Initialize SDL2 + + // Create an application window with the following settings: + window = SDL_CreateWindow( + "An SDL2 window", // window title + SDL_WINDOWPOS_UNDEFINED, // initial x position + SDL_WINDOWPOS_UNDEFINED, // initial y position + 640, // width, in pixels + 480, // height, in pixels + SDL_WINDOW_OPENGL // flags - see below + ); + + // Check that the window was successfully created + if (window == NULL) { + // In the case that the window could not be made... + printf("Could not create window: %s\n", SDL_GetError()); + return 1; + } + + // The window is open: could enter program loop here (see SDL_PollEvent()) + // Setup renderer + SDL_Renderer* renderer = NULL; + renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED); + + // Set render color to red ( background will be rendered in this color ) + SDL_SetRenderDrawColor( renderer, 255, 0, 0, 255 ); + + // Clear winow + SDL_RenderClear( renderer ); + + // bouyatest + + // Creat a rect at pos ( 50, 50 ) that's 50 pixels wide and 50 pixels high. + SDL_Rect r; + r.x = 50; + r.y = 50; + r.w = 500; + r.h = 500; + + // Set render color to blue ( rect will be rendered in this color ) + SDL_SetRenderDrawColor( renderer, 0, 200, 255, 255 ); + + // Render the rect to the screen + SDL_RenderPresent(renderer); + + SDL_Delay(1000); // Pause execution for 3000 milliseconds, for example + + // Close and destroy the window + SDL_DestroyWindow(window); + + // Clean up + SDL_Quit(); + return 0; +} diff --git a/build-android/src/physics.c b/build-android/src/physics.c new file mode 100644 index 0000000..1eb9348 --- /dev/null +++ b/build-android/src/physics.c @@ -0,0 +1,2 @@ +#include "physics.h" +#include "types.h" diff --git a/build-android/src/physics.h b/build-android/src/physics.h new file mode 100644 index 0000000..e69de29 diff --git a/build-android/src/types.h b/build-android/src/types.h new file mode 100644 index 0000000..8f641d3 --- /dev/null +++ b/build-android/src/types.h @@ -0,0 +1,236 @@ + +#ifndef PTYPES_H +#define PTYPES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "colours.h" +#include "datatypes.h" +#include "vect.h" + +enum motors { + M_GRAVITY = 0, + M_FRICTION = 1, + M_PLAYER_WALK = 2, + M_WINCH = 3 +}; + +enum world_thing_kind { + PLAYER_W = 0, + FLOOR = 1, + CEILING = 2, + ROOM_W, + STATIC_WALL_W, + PROJECTILE +}; + + +// used to exert a force on an object +typedef struct motorstruct { + double x; // positive is right + double y; // positive is down + double torque; // positive is anticlockwise + double max_velocity; // max motor output velocity + // does not apply force if the velocity in the + // direction of the motor vector is greater than + // max_velocity + + struct timespec timeout;// absolute time (in ns, from when the program starts) + // for how long the motor runs before stopping + // automatically + // set to -1 for infinity + bool stop; // turn the motor off or on + void (*update_motor)(struct motorstruct *motor); // function pointer for generating + // the motor's output curve + struct BodyStruct *end_object; +} Motor; + +typedef struct { + int x; + int y; +} Point; + +/* String structure, used as a sub-element of a Body, compressible, but not + * stretchable. */ +struct String { + bool attached; + double max_length; + Vect end_point; + int (*update_end_point)(struct String*); // method to update the end pt + // say if string is attached + // to another object + int (*set_end_point)(struct String*, Vect *); // manually set the end point of + // string +}; + +typedef struct String String; + +typedef struct BodyStruct{ + // turn on dynamic physics + // For moving objects. + // eg. on for payer, off for walls + bool dynamics; + + // unique identifier + int uid; + + bool colliding; + int was_colliding; + + // position in viewport (pixels) + SDL_Point screen_pos; + SDL_mutex * lock; + + // SI Unit kinematics + /*------------------*/ + Vect position; + Vect next_position; // used for casting collision + Vect vel; + Vect acc; + + // properties + double obj_mass; // kgs + double obj_elasticity; // rho + double obj_friction; // between 0 and 1 (fraction of lateral velocity + // that is removed) + + //float x_vel; + //float y_vel; + + //float x_acc; + //float y_acc; + /*------------------*/ + + // collisions + Vect *collision_poly; + Vect *collision_shape; + int collision_poly_size; + int collision_shape_size; + void (*updateCollisionPoly)(struct BodyStruct *); + + // fields + double glob_friction; + bool glob_gravity; // t/f + +// uint32_t last_advance_time; + + struct timespec last_advance_time; + + // applying forces + int num_motors; + int max_motors; + Motor *motors; + + int num_strings; + int max_strings; + String *strings; + +} Body; + +typedef struct { + bool has_physics; + Body *physics; + int max_walking_speed; + int colliding; +} player_st; + +typedef struct { + int numNodes; + SDL_Point *nodes; + Body *physics; +} Wall; + +typedef struct { + Vect left; + Vect right; + Body *physics; +} FloorPoly; + +typedef struct { + FloorPoly *polys; + int numPolys; +} Floor; + +struct physics_collection { + int numItems; + Body **items; +}; + +typedef struct Projectile { + Body *physics; + void*(*on_collision)(struct Projectile*); + void*(*on_step)(struct Projectile*); +} Projectile; + +struct colour_pallete { + struct colour bg; + struct colour fg1; + struct colour fg2; + struct colour fg3; +}; + +struct environment { + int level; + Vect position; + ArrayList ceil; // doubles + ArrayList floor; // doubles + ArrayList bg1; // doubles + ArrayList bg2; // doubles + struct colour_pallete colours; + struct physics_collection physics; +}; + +struct room { + struct environment env; + struct physics_collection ceil; + struct physics_collection floor; +}; + +struct world_thing { + enum world_thing_kind kind; + int nid; + bool collisions; + bool physics; + // free function for the below type + void (*free)(void *); + union { + player_st *player; + Wall *wall; + Floor *floor; + Projectile *projectile; + struct room *room; + }; +}; + +typedef struct world_thing world_thing; + +struct world { + world_thing (*get)(int i); + ArrayList items; + int modified; + world_thing** uniques_index; +}; + +typedef struct world GlobWorld; + +// environment + +enum { +// E_ROOM_WIDTH = 100000, + E_ROOM_WIDTH = 10000, + E_ROOM_RES = 500, + E_ROOM_TILES = E_ROOM_WIDTH / E_ROOM_RES, +}; + +#endif diff --git a/build-android/src/vect.c b/build-android/src/vect.c new file mode 100644 index 0000000..ff123bf --- /dev/null +++ b/build-android/src/vect.c @@ -0,0 +1,81 @@ +#include "vect.h" + +double vect_dot(Vect V, Vect B) { + return V.x * B.x + V.y * B.y; +} + +double vect_mag(Vect v) { + double mag = sqrt((v.x * v.x) + (v.y * v.y)); + return mag; +} + +Vect vect_scalar(Vect V, double s) { + Vect ret; + ret.x = V.x * s; + ret.y = V.y * s; + return ret; +} + +double vect_arg(Vect v) { + return atan2(v.y, v.x); +} + +Vect vect_modarg(double mod, double arg) { + Vect ret; + ret.x = mod * cos(arg); + ret.y = mod * sin(arg); + return ret; +} + +Vect vect_rotate(Vect v, double radians) { + Vect res; + + res.x = v.x * cos(radians) - v.y * sin(radians); + res.y = v.x * sin(radians) + v.y * cos(radians); + return res; +} + +Vect vect_add(Vect v1, Vect v2) { + Vect res; + res.x = v1.x + v2.x; + res.y = v1.y + v2.y; + return res; +} + +// project one onto two +Vect project_vect(Vect one, Vect two) { + // $$ a \cdot \frac {|b|} {b} $$ + Vect unittwo; + unittwo.x = two.x / vect_mag(two); + unittwo.y = two.y / vect_mag(two); + + Vect proj; + proj.x = unittwo.x * one.x; + proj.y = unittwo.y * one.y; + + return proj; +} + +// project V onto P +double old_vect_scalar_projection(Vect V, Vect P) { + double angle = vect_dir(V) - vect_dir(P); + return cos(angle) * vect_mag(V); +} + +double vect_scalar_projection(Vect v, Vect p) { + double num = vect_dot(v, p); + return num / vect_mag(p); +} + +double vect_dir(Vect V) { + return atan2(V.y, V.x); +} + +// distance between two points +double vect_distance(Vect A, Vect B) { + double y = (B.y - A.y); + double x = (B.x - A.x); + y = y * y; + x = x * x; + return sqrt(x + y); +} diff --git a/build-android/src/vect.h b/build-android/src/vect.h new file mode 100644 index 0000000..c711a8d --- /dev/null +++ b/build-android/src/vect.h @@ -0,0 +1,45 @@ + +#ifndef VECT_H +#define VECT_H +#include + +// origin-centred vector +typedef struct { + double x; + double y; +} Vect; + +/* Return the magnitude of two vectors */ +double vect_mag(Vect v); + +/* Return the angle of a vector in radians (using atan2) */ +double vect_dir(Vect V); + +/* Return the dot product of two vectors */ +double vect_dot(Vect V, Vect B); + +/* return a vector multiplied by a scalar */ +Vect vect_scalar(Vect V, double s); + +/* Return the sum of two vectors */ +Vect vect_add(Vect v1, Vect v2); + +/* Return the projection of vector one onto vector two */ +Vect project_vect(Vect one, Vect two); + +/* Return the scalar projection of V onto P */ +double vect_scalar_projection(Vect V, Vect P); + +/* Return the vector v rotated by radians radians. */ +Vect vect_rotate(Vect v, double radians); + +/* Return the argument of v in radians */ +double vect_arg(Vect v); + +/* create a vector using modarg form */ +Vect vect_modarg(double mod, double arg); + +/* Return the distance between two point vectors A and B */ +double vect_distance(Vect A, Vect B); + +#endif