├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── brackeen │ ├── app │ ├── App.java │ ├── BitmapFont.java │ ├── audio │ │ ├── AudioBuffer.java │ │ ├── AudioEngine.java │ │ └── AudioStream.java │ ├── package-info.java │ └── view │ │ ├── Button.java │ │ ├── ImageView.java │ │ ├── Label.java │ │ ├── Scene.java │ │ └── View.java │ └── scared │ ├── BaseConsoleScene.java │ ├── CollisionDetection.java │ ├── ConsoleScene.java │ ├── GameScene.java │ ├── LoadingScene.java │ ├── Main.java │ ├── Map.java │ ├── MessageQueue.java │ ├── Settings.java │ ├── SoftRender3D.java │ ├── SoftTexture.java │ ├── SoundPlayer3D.java │ ├── Stats.java │ ├── Tile.java │ ├── action │ ├── Action.java │ ├── DoorAction.java │ ├── GeneratorAction.java │ └── MovableWallAction.java │ └── entity │ ├── Ammo.java │ ├── BlastMark.java │ ├── Enemy.java │ ├── Entity.java │ ├── Key.java │ ├── MedKit.java │ └── Player.java ├── non-packaged-resources └── index.html └── resources ├── background └── background.png ├── enemy ├── 0.png ├── 1.png ├── 10.png ├── 11.png ├── 12.png ├── 13.png ├── 14.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png └── 9.png ├── hud ├── crosshair.png ├── gun01.png └── gun02.png ├── maps ├── level0.txt ├── level1.txt ├── level2.txt ├── level3.txt ├── level4.txt ├── level5.txt └── level6.txt ├── sound ├── bigfan.wav ├── door_unlock.wav ├── doorclose.wav ├── doorwoosh.wav ├── endlevel.wav ├── enemy_dead.wav ├── getammo.wav ├── laser0.wav ├── laser1.wav ├── no_ammo.wav ├── nuclear_health.wav ├── player_dead.wav ├── player_hurt.wav ├── startlevel.wav ├── unlock.wav └── wallmove.wav ├── sprites ├── ammo.png ├── blast1.png ├── blast2.png ├── blast3.png ├── key01.png ├── key02.png ├── key03.png ├── medkit.png └── nuclear.png ├── textures ├── door00.png ├── door01.png ├── door02.png ├── door03.png ├── exit00.png ├── exit01.png ├── generator00.png ├── generator01.png ├── wall00.png ├── wall01.png ├── wall02.png ├── wall03.png ├── wall04.png ├── wall05.png ├── wall06.png ├── wall07.png ├── wall08.png ├── wall09.png ├── wall10.png ├── wall11.png ├── wall12.png ├── wall13.png ├── wall14.png ├── wall15.png └── window00.png └── ui ├── console_font.png ├── message_font.png └── score_font.png /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | 4 | # Auto-generated IDEA/Android Studio files 5 | .idea 6 | *.iml 7 | local.properties -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## Version 1.9.1 (September 21, 2016) 4 | * Fix key input issue on macOS Sierra. 5 | 6 | ## Version 1.9 (July 19, 2016) 7 | * Classic: Use original fonts, original gun, original 64x64 enemies, no title or help screen. 8 | * Resizable window with pixel scaling. 9 | * Better 3D audio (panning, real-time volume changes). 10 | * Added stats (Hit accuracy, time, etc). 11 | * A few texture cleanups, improved exit texture. 12 | * Restore feature: Show fps. 13 | * Settings (from the console): Depth cueing, auto pixel scaling, audio volume. 14 | * New console font. 15 | * Many rendering/raycast fixes. 16 | * Console history (like bash). 17 | * Improved gun display when dead. 18 | * Create mipmaps at runtime. 19 | * Game over tweaks: Show stats on win, delay before “click to continue”, allow spacebar to continue. 20 | * Lower CPU usage, especially on Java 8. 21 | * Convert to gradle / Android Studio. 22 | * Code cleanup (Java 7, warnings, analyzer issues). 23 | * Code style cleanup (braces, etc). 24 | * Requires Java 7. 25 | 26 | ## Version 1.8.1 (December 15, 2015) 27 | * Fixed audio crash on some Linux systems 28 | 29 | ## Version 1.8 (June 6, 2013) 30 | * Made the jar executable 31 | * Hide default cursor 32 | 33 | ## Version 1.7 (February 11, 2012) 34 | * Open source 35 | * Removed “show fps” 36 | * Removed mute option 37 | * Removed PulpCore. Lost features: 38 | - No pulpcore.js 39 | - No audio panning 40 | - No pack file 41 | - No Ogg Vorbis 42 | - Probably other things 43 | 44 | ## Version 1.6.3 (April 15, 2009) 45 | * "Official" PulpCore 0.11.5 (displaymode fix for Java 1.4.2) 46 | 47 | ## Version 1.6.2 (April 14, 2009) 48 | * Fixed cursor bug introduced in 1.6.1 49 | * Added depth cueing 50 | * Ported to "almost official" 0.11.5 51 | * Update JOrbis 52 | 53 | ## Version 1.6.1 (Dec 15, 2008) 54 | * Made a pack file 55 | * Ported to PulpCore 0.11.5 56 | 57 | ## Version 1.5.3 (Jan 19, 2008) 58 | * Fixed another mute/unmute bug 59 | * Fixed possible division by zero 60 | * Fixed broken Pulp logo 61 | 62 | ## Version 1.5.2 (Jan 13, 2008) 63 | * Ported to PulpCore 0.10.10 64 | 65 | ## Version 1.5.1 (Oct 21, 2007) 66 | * Ported to PulpCore 0.10.4 (pulpcore.js fix) 67 | 68 | ## Version 1.5.0 (Oct 11, 2007) 69 | * Ported to PulpCore 0.10 70 | * Invisible cursor, larger crosshair 71 | * Red health font color if health <= 20 72 | * 3D sound: panning for doors, robot sounds, and generator 73 | * OGG support 74 | 75 | ## Version 1.4.2 (May 24, 2007) 76 | * Ported to PulpCore 0.9 77 | * New pulpcore.js 78 | 79 | ## Version 1.4.1 (May 22, 2007) 80 | * Hijack detection fix 81 | * Codebase fix for frames sites 82 | 83 | ## Version 1.4 (May 20, 2007) 84 | * Ported to PulpCore 0.8 85 | 86 | ## Version 1.3.2 (April 18, 2007) 87 | * Crosshair is always shown 88 | * Max of 100 ms elapsed per frame 89 | 90 | ## Version 1.3.1 (March 27, 2007) 91 | * Update to PulpCore 0.7.0 92 | * New Help/About scene 93 | * "New Game" confirm 94 | * Change behavior of "show status", and changed the key to ESCAPE 95 | * First release on pulpgames.net. 96 | 97 | ## Version 1.3 98 | * Changed the palette gamma (modern monitors are a lot more brighter) 99 | * Anti-aliased gun and message text 100 | * Fixed robot "float" problem 101 | * Higher resolution robots (128x128 instead of 64x64) 102 | * Faster door open time 103 | * Click to fire / New mouse aiming. 104 | - Weapons hurt robots less 105 | - Robots have a 33% chance to fire immediately after getting hurt 106 | - If an enemy is about to fire, and it gets hurt, there is only a 50% chance of the firing to be interrupted (previously it was 100%) 107 | * Mute button only shown if the sound system is available 108 | * Sound preference is saved between sessions 109 | * Start screen has a "continue" option if there is a saved level. 110 | * Converted to PulpCore architecture 111 | * Handlers used instead of new Threads 112 | 113 | ## Version 1.2 (October 6, 2006) 114 | * Changed view size to 550x400 115 | * Game now appears smoother at higher frame rates. 116 | * New crosshair appearance. 117 | * New gun appearance. 118 | * New "Window"/fence appearance. 119 | * Blast is shown if the player's laser hits a wall. This helps players aim. 120 | * Gun bobbing while running. 121 | * Door-side texture. 122 | * Actions are performed automatically - doors open when you are near them, wall switches activate when you bump against them. 123 | * Fire weapon action is now binded to the spacebar. 124 | * Doors open faster and allow the player to move through them and fire though them when they are 3/4 open. 125 | * Controls are smoother (slower turn velocity), which helps players aim. 126 | * Updated laser-fire sound. 127 | * A sound is now heard when the player has no ammo. 128 | * Fixed aiming issues. 129 | * Rearranged the levels to push harder levels to later in the game. 130 | * New in-game console (no longer a popup window). 131 | * In-game Mute button. 132 | * In-game help. 133 | * New startup screen. 134 | * New splash screen. 135 | * Better sound engine for Java 1.3+. 136 | * Robust downloading, with retry on failure. 137 | 138 | ## Version 1.1.7 (October 19, 2004) 139 | * Java 1.4 and Java 5: Allowed Tab key 140 | * Java 5: Fixed sound problem. 141 | * Java 5: Uses High-resolution timer for smoother graphics and gameplay. 142 | 143 | ## Version 1.1.6 (May 1, 2002) 144 | * Fixed a bug introduced in the last version: gameplay is jerky in on some machines. 145 | 146 | ## Version 1.1.5 (April 19, 2002) 147 | * Allowed "translate.google.com" as a doc host 148 | * Fixed sounds and performance problems on java 1.4.0 149 | * Fixed hang-bug that appeared most noticeably on level 3. 150 | * Capped the frame rate to 60fps. 151 | 152 | ## Version 1.1.4 (August 9, 1999) 153 | * Added security features. (The code must be on a particular host and the html document must be on a particular host) 154 | 155 | ## Version 1.1.3 (June 6, 1999) 156 | * Changed the game controls based on user feedback. 157 | * Fixed yet another DoorHandler bug for people with fast Internet access (like a cable modem). The DoorHandler code kinda assumed it took a second or two to load a level, but that wasn't always the case, and it ended up hogging the processor. 158 | * Added a "Load Sounds" checkbox at the beginning, so you can choose whether you want to load the sounds on startup. 159 | * When you get hit, the border of the game will flash. This is very useful if you play the game without sound. 160 | * If the game loses input focus (like if you click on another part of the page), the a message will pop up telling you to click on the game. 161 | * Made the game controls list a little more comprehensive. 162 | 163 | ## Version 1.1.2 (October 26, 1998) 164 | * Hold tab to see stats 165 | * Added strafing 166 | * Fixed problem with DoorHandler/MovableWallHandler if starting Java threads on your system is slow. 167 | * All the class files are now stored in a compressed JAR, so it's faster to download. 168 | 169 | ## Version 1.1.1 (July 31, 1998) 170 | * Smoother framerate 171 | * Fixed bug when pressing reload/refresh in the browser 172 | * Warning for users with Java 1.0 173 | * Fixed framerate calculation 174 | 175 | ## Version 1.1 (March 22, 1998) 176 | * Fixed MovableWallHandler bug which was the same as the DoorHandler bug. 177 | * Fixed several aiming bugs. You can now kill an enemy with 2-4 shots, depending on how good your aim is. 178 | * Keys now appear on the screen when you get them. 179 | * Press 'x' to show the aiming crosshair. 180 | * Created the Scared console. Cheat codes, anyone? 181 | * Redid and/or touched up a few of the images. 182 | * Added new item: Nuclear Health (appears on levels 3 and 5) 183 | * Made a better death sequence. Before, the game froze when you died. Now, you fall to the ground and cannot move, but you can turn your head to see who killed you. 184 | * Fixed bug with the secret walls (movable walls) that put the texture of the secret wall on the floor when it moved. 185 | * Added three more levels and touched up the existing three levels. Also, I rearranged the order of the levels. Level 3 is now Level 5. 186 | 187 | ## Version 1.0.1 (March 10, 1998) 188 | * Fixed DoorHandler bug appearing on some Windows NT systems. 189 | 190 | ## Version 1.0 (March 1, 1998) 191 | * Initial Release 192 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 1998-2012, David Brackeen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of David Brackeen nor the names of its contributors may 13 | be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scared 2 | 3 | Scared is a 3D shooter in the style of Wolfenstein 3D. 4 | 5 | It was originally written as a Java applet in 1998. 6 | 7 | ## Download 8 | 9 | An executable jar is available here: [http://www.brackeen.com/scared/](http://www.brackeen.com/scared/) 10 | 11 | ## Building 12 | 13 | The source is organized as a Gradle project. You can build it from an IDE or from the command line. 14 | 15 | Assuming [Git](https://help.github.com/articles/set-up-git) and 16 | [JDK 7 or newer](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 17 | is installed, open a terminal and enter: 18 | ``` 19 | git clone https://github.com/brackeen/Scared.git 20 | cd Scared 21 | ./gradlew build 22 | ``` 23 | An executable jar is created in the `build/libs` folder. 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | group = 'com.brackeen' 4 | version = '1.9.1' 5 | 6 | description = 'Scared' 7 | 8 | sourceCompatibility = 1.7 9 | targetCompatibility = 1.7 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | jar { 16 | manifest { 17 | attributes('Main-Class': 'com.brackeen.scared.Main', 18 | 'Implementation-Title': 'Scared', 19 | 'Implementation-Version': version) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 15 20:34:46 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Scared' 2 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/BitmapFont.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.image.BufferedImage; 5 | 6 | @SuppressWarnings("unused") 7 | public class BitmapFont { 8 | 9 | private final BufferedImage image; 10 | private final int charWidth; 11 | private final int charHeight; 12 | private final char firstChar; 13 | private final int numChars; 14 | private final boolean hasLowercase; 15 | private int tracking = 0; 16 | 17 | public BitmapFont(BufferedImage image, int charWidth, char firstChar) { 18 | this.image = image; 19 | this.charWidth = charWidth; 20 | this.charHeight = image.getHeight(); 21 | this.firstChar = firstChar; 22 | this.numChars = image.getWidth() / charWidth; 23 | this.hasLowercase = firstChar <= 'a' && firstChar + numChars >= 'z'; 24 | } 25 | 26 | public int getTracking() { 27 | return tracking; 28 | } 29 | 30 | public void setTracking(int tracking) { 31 | this.tracking = tracking; 32 | } 33 | 34 | public int getStringWidth(String s) { 35 | if (s == null) { 36 | return 0; 37 | } else { 38 | return s.length() * charWidth + (s.length() - 1) * tracking; 39 | } 40 | } 41 | 42 | public int getHeight() { 43 | return charHeight; 44 | } 45 | 46 | public boolean canDisplay(char ch) { 47 | if (Character.isLowerCase(ch) && !hasLowercase) { 48 | ch = Character.toUpperCase(ch); 49 | } 50 | return (ch >= firstChar && ch < firstChar + numChars); 51 | } 52 | 53 | public void drawString(Graphics2D g, String s) { 54 | if (s != null) { 55 | int x = 0; 56 | int y = 0; 57 | for (int i = 0; i < s.length(); i++) { 58 | char ch = s.charAt(i); 59 | if (Character.isLowerCase(ch) && !hasLowercase) { 60 | ch = Character.toUpperCase(ch); 61 | } 62 | if (ch >= firstChar && ch < firstChar + numChars) { 63 | int charX = (ch - firstChar) * charWidth; 64 | g.drawImage(image, 65 | x, y, x + charWidth, y + charHeight, 66 | charX, 0, charX + charWidth, charHeight, null); 67 | } 68 | x += charWidth + tracking; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/audio/AudioBuffer.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.audio; 2 | 3 | 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.DataInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URL; 9 | 10 | import javax.sound.sampled.AudioFormat; 11 | import javax.sound.sampled.AudioInputStream; 12 | import javax.sound.sampled.AudioSystem; 13 | import javax.sound.sampled.UnsupportedAudioFileException; 14 | 15 | public class AudioBuffer { 16 | public static final AudioBuffer BLANK_AUDIO = new AudioBuffer(null, null); 17 | 18 | private final AudioFormat format; 19 | final byte[] data; 20 | 21 | public AudioBuffer(AudioFormat format, byte[] data) { 22 | this.format = format; 23 | this.data = data; 24 | } 25 | 26 | public AudioFormat getFormat() { 27 | return format; 28 | } 29 | 30 | /** 31 | * Plays this AudioBuffer. 32 | * @param volume The volume, from 0 to 1. 33 | * @param pan The pan, from -1 (left channel) to 0 (center) to 1 (right channel). 34 | * @param loop Whether to loop the audio. If true, the looping continues indefinitely until {@link AudioStream#stop()} is called. 35 | * @return An AudioStream if playback started, or null if there are no AudioStreams available 36 | * (all streams are currently playing). 37 | */ 38 | public AudioStream play(float volume, float pan, boolean loop) { 39 | AudioStream stream = null; 40 | if (data != null) { 41 | stream = AudioEngine.getAvailableStream(); 42 | if (stream != null) { 43 | stream.play(this, volume, pan, loop); 44 | } 45 | } 46 | return stream; 47 | } 48 | 49 | /** 50 | * Plays this AudioBuffer. 51 | * @return An AudioStream if playback started, or null if there are no AudioStreams available 52 | * (all streams are currently playing). 53 | */ 54 | public AudioStream play() { 55 | return play(1, 0, false); 56 | } 57 | 58 | /** 59 | * Loops this AudioBuffer. The looping continues indefinitely until {@link AudioStream#stop()} is called. 60 | * @return An AudioStream if playback started, or null if there are no AudioStreams available 61 | * (all streams are currently playing). 62 | */ 63 | public AudioStream loop() { 64 | return play(1, 0, true); 65 | } 66 | 67 | public static AudioBuffer read(URL url) throws IOException { 68 | if (url == null) { 69 | return null; 70 | } 71 | 72 | // Read input data 73 | AudioFormat format; 74 | byte[] data; 75 | try (AudioInputStream is = AudioSystem.getAudioInputStream(url)) { 76 | format = is.getFormat(); 77 | if (format == null) { 78 | return null; 79 | } 80 | long frameLength = is.getFrameLength(); 81 | int length; 82 | if (frameLength > 0 && format.getFrameSize() > 0) { 83 | length = (int) frameLength * format.getFrameSize(); 84 | } else { 85 | length = -1; 86 | } 87 | data = readFully(is, length); 88 | } catch (IllegalArgumentException | UnsupportedAudioFileException ex) { 89 | // org.classpath.icedtea.pulseaudio may throw IllegalArgumentException? 90 | throw new IOException(ex); 91 | } 92 | 93 | // Use 8000hz instead of 8012hz or 8016hz 94 | if (format.getFrameRate() > 8000 && format.getFrameRate() < 8100) { 95 | format = new AudioFormat(8000, format.getSampleSizeInBits(), format.getChannels(), 96 | format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED, format.isBigEndian()); 97 | } 98 | 99 | // Convert to stereo so that the Pan control works. 100 | if (format.getChannels() == 1) { 101 | format = new AudioFormat(format.getSampleRate(), format.getSampleSizeInBits(), 2, 102 | format.getEncoding() == AudioFormat.Encoding.PCM_SIGNED, format.isBigEndian()); 103 | byte[] dstData = new byte[data.length * 2]; 104 | for (int srcOffset = 0, dstOffset = 0; srcOffset < data.length; srcOffset += 2, dstOffset += 4) { 105 | dstData[dstOffset] = data[srcOffset]; 106 | dstData[dstOffset + 1] = data[srcOffset + 1]; 107 | dstData[dstOffset + 2] = data[srcOffset]; 108 | dstData[dstOffset + 3] = data[srcOffset + 1]; 109 | } 110 | data = dstData; 111 | } 112 | 113 | return new AudioBuffer(format, data); 114 | } 115 | 116 | private static byte[] readFully(InputStream is, int length) throws IOException { 117 | if (length > 0) { 118 | byte[] data = new byte[length]; 119 | DataInputStream dis = new DataInputStream(is); 120 | dis.readFully(data); 121 | return data; 122 | } else { 123 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 124 | byte[] buffer = new byte[16384]; 125 | int bytesRead; 126 | while ((bytesRead = is.read(buffer, 0, buffer.length)) != -1) { 127 | os.write(buffer, 0, bytesRead); 128 | } 129 | os.flush(); 130 | return os.toByteArray(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/audio/AudioEngine.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.audio; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.atomic.AtomicBoolean; 6 | 7 | import javax.sound.sampled.AudioFormat; 8 | import javax.sound.sampled.AudioSystem; 9 | import javax.sound.sampled.DataLine; 10 | import javax.sound.sampled.LineUnavailableException; 11 | import javax.sound.sampled.Mixer; 12 | import javax.sound.sampled.SourceDataLine; 13 | 14 | /** 15 | * An alternative to using the Java Sound API's Clip, which have the following issues: 16 | * * Only 32 Clips can be loaded on some systems. 17 | * * Volume/pan changes can't happen in real-time because of Clip's large internal buffer. 18 | * 19 | * Features of this audio engine: 20 | * * Unlimited AudioBuffers can be loaded (limited only by memory). 21 | * * Real-time volume/pan changes. 22 | * * Up to 40 simultaneous playback streams. 23 | * * Low-latency. 24 | * * Looping. 25 | * 26 | * Notes: 27 | * * Audio files are loaded into memory. 28 | * * All audio must have the same sample rate. 29 | * * All 1-channel (mono) audio files are converted to stereo. 30 | * * No mp3/ogg support. 31 | * 32 | * Simple Example: 33 | * AudioEngine.init(44100); 34 | * ... 35 | * AudioBuffer audioBuffer = AudioBuffer.read(fileUrl); 36 | * audioBuffer.play(); 37 | * ... 38 | * AudioEngine.destroy(); 39 | * 40 | * Real-time changes example: 41 | * AudioEngine.init(44100); 42 | * ... 43 | * AudioBuffer audioBuffer = AudioBuffer.read(fileUrl); 44 | * AudioStream stream = audioBuffer.play(); 45 | * ... 46 | * stream.setPan(-1.0f); 47 | * ... 48 | * stream.setVolume(0.5f); 49 | * ... 50 | * AudioEngine.destroy(); 51 | */ 52 | public class AudioEngine { 53 | private static final int MAX_SIMULTANEOUS_SOUNDS = 40; 54 | 55 | private static Context context; 56 | private static float masterVolume = 1.0f; 57 | 58 | public static float getMasterVolume() { 59 | return masterVolume; 60 | } 61 | 62 | public static void setMasterVolume(float masterVolume) { 63 | AudioEngine.masterVolume = masterVolume; 64 | } 65 | 66 | /** 67 | * Initializes the audio engine. The method must be called before playing audio. 68 | * @param frameRate The audio frame rate. Typically supported: 8000, 11025, 22050, or 44100. 69 | * All audio files will be played at this rate, regardless of the audio files' 70 | * internal frame rate. 71 | */ 72 | public static void init(float frameRate) { 73 | if (context == null) { 74 | context = new Context(frameRate); 75 | context.start(); 76 | } 77 | } 78 | 79 | public static void destroy() { 80 | if (context != null) { 81 | context.destroy(); 82 | context = null; 83 | } 84 | } 85 | 86 | static AudioStream getAvailableStream() { 87 | if (context == null) { 88 | throw new IllegalStateException("AudioEngine.init not called."); 89 | } 90 | return context.getAvailableStream(); 91 | } 92 | 93 | static void notifySoundAdded() { 94 | final Context currContext = context; 95 | if (currContext != null) { 96 | currContext.notifySoundAdded(); 97 | } 98 | } 99 | 100 | private static class Context implements Runnable { 101 | private final Object stateLock = new Object(); 102 | private final Object updateLock = new Object(); 103 | 104 | private Thread renderThread; 105 | private final AudioFormat audioFormat; 106 | private final AtomicBoolean running = new AtomicBoolean(true); 107 | 108 | private Mixer mixer; 109 | private final List streams = new ArrayList<>(); 110 | 111 | private Context(float frameRate) { 112 | audioFormat = new AudioFormat(frameRate, 16, 2, true, false); 113 | } 114 | 115 | private void start() { 116 | synchronized (stateLock) { 117 | renderThread = new Thread(this, "AudioEngine"); 118 | renderThread.setDaemon(true); 119 | renderThread.setPriority(Thread.MAX_PRIORITY); 120 | renderThread.start(); 121 | try { 122 | stateLock.wait(1000); 123 | } catch (InterruptedException ex) { 124 | // Ignore 125 | } 126 | } 127 | } 128 | 129 | private void destroy() { 130 | synchronized (stateLock) { 131 | running.set(false); 132 | try { 133 | stateLock.wait(1000); 134 | } catch (InterruptedException ex) { 135 | // Ignore 136 | } 137 | } 138 | } 139 | 140 | private AudioStream getAvailableStream() { 141 | for (AudioStream stream : streams) { 142 | if (stream.isAvailable()) { 143 | return stream; 144 | } 145 | } 146 | return null; 147 | } 148 | 149 | private void notifySoundAdded() { 150 | synchronized (updateLock) { 151 | updateLock.notify(); 152 | } 153 | } 154 | 155 | //region Methods called on the audio thread 156 | 157 | @Override 158 | public void run() { 159 | // Create mixer 160 | try { 161 | mixer = AudioSystem.getMixer(null); 162 | } catch (IllegalArgumentException ex) { 163 | Mixer.Info[] mixerInfoArray = AudioSystem.getMixerInfo(); 164 | for (Mixer.Info mixerInfo : mixerInfoArray) { 165 | try { 166 | mixer = AudioSystem.getMixer(mixerInfo); 167 | if (mixer != null) { 168 | break; 169 | } 170 | } catch (IllegalArgumentException ex2) { 171 | // Ignore 172 | } 173 | } 174 | } 175 | 176 | // Create streams 177 | if (mixer != null) { 178 | int maxStreams = getMaxSimultaneousSounds(); 179 | DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); 180 | for (int i = 0; i < maxStreams; i++) { 181 | try { 182 | SourceDataLine line = (SourceDataLine) mixer.getLine(lineInfo); 183 | line.open(audioFormat); 184 | if (line.isOpen()) { 185 | streams.add(new AudioStream(audioFormat, line)); 186 | } 187 | } catch (LineUnavailableException | IllegalArgumentException ex) { 188 | // Ignore 189 | } 190 | } 191 | } 192 | if (streams.isEmpty()) { 193 | destroy(); 194 | } 195 | 196 | // Notify started 197 | synchronized (stateLock) { 198 | stateLock.notify(); 199 | } 200 | 201 | // Run 202 | while (running.get()) { 203 | synchronized (updateLock) { 204 | for (AudioStream stream : streams) { 205 | stream.tick(); 206 | } 207 | try { 208 | updateLock.wait(1); 209 | } catch (InterruptedException ex) { 210 | // Ignore 211 | } 212 | } 213 | } 214 | 215 | // Cleanup 216 | for (AudioStream stream : streams) { 217 | stream.close(); 218 | } 219 | streams.clear(); 220 | if (mixer != null) { 221 | mixer.close(); 222 | mixer = null; 223 | } 224 | 225 | // Notify destroyed 226 | synchronized (stateLock) { 227 | stateLock.notify(); 228 | } 229 | } 230 | 231 | private int getMaxSimultaneousSounds() { 232 | try { 233 | DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); 234 | int maxLines = mixer.getMaxLines(lineInfo); 235 | if (maxLines == AudioSystem.NOT_SPECIFIED || maxLines > MAX_SIMULTANEOUS_SOUNDS) { 236 | return MAX_SIMULTANEOUS_SOUNDS; 237 | } else { 238 | return maxLines; 239 | } 240 | } catch (Exception ex) { 241 | return 0; 242 | } 243 | } 244 | 245 | //endregion 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/audio/AudioStream.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.audio; 2 | 3 | 4 | import javax.sound.sampled.AudioFormat; 5 | import javax.sound.sampled.FloatControl; 6 | import javax.sound.sampled.SourceDataLine; 7 | 8 | /** 9 | * AudioStreams are created when playback starts, and can be used to modify volume/pan, or stop the 10 | * playback early. 11 | * @see AudioBuffer#play() 12 | */ 13 | public class AudioStream { 14 | private static byte[] blankData = new byte[4096]; 15 | 16 | private final AudioFormat format; 17 | private final SourceDataLine line; 18 | 19 | private AudioBuffer buffer; 20 | private boolean loop = false; 21 | private float volume; 22 | private float pan; 23 | 24 | private int playbackPos; 25 | private float playbackVolume; 26 | private float playbackPan; 27 | 28 | AudioStream(AudioFormat format, SourceDataLine line) { 29 | this.format = format; 30 | this.line = line; 31 | } 32 | 33 | boolean isAvailable() { 34 | synchronized (this) { 35 | return buffer == null; 36 | } 37 | } 38 | 39 | public AudioBuffer getBuffer() { 40 | return buffer; 41 | } 42 | 43 | /** 44 | * Sets the linear volume of the line from 0 to 1. 45 | */ 46 | public void setVolume(float volume) { 47 | this.volume = volume; 48 | } 49 | 50 | /** 51 | * Sets the pan from -1 (left channel) to 0 (center) to 1 (right channel). 52 | */ 53 | public void setPan(float pan) { 54 | this.pan = pan; 55 | } 56 | 57 | public void setLoop(boolean loop) { 58 | this.loop = loop; 59 | } 60 | 61 | void play(AudioBuffer buffer, float volume, float pan, boolean loop) { 62 | synchronized (this) { 63 | this.buffer = buffer; 64 | this.volume = volume; 65 | this.pan = pan; 66 | this.loop = loop; 67 | this.playbackPos = 0; 68 | this.playbackVolume = Float.MIN_VALUE; 69 | this.playbackPan = Float.MIN_VALUE; 70 | } 71 | AudioEngine.notifySoundAdded(); 72 | } 73 | 74 | public void stop() { 75 | synchronized (this) { 76 | if (buffer != null) { 77 | loop = false; 78 | playbackPos = buffer.data.length; 79 | } 80 | } 81 | } 82 | 83 | //region Private methods called on the audio thread 84 | 85 | void close() { 86 | if (line.isOpen()) { 87 | // TODO: This might cause a glitch if audio is currently playing? 88 | line.close(); 89 | } 90 | } 91 | 92 | void tick() { 93 | if (buffer == null) { 94 | return; 95 | } 96 | synchronized (this) { 97 | if (buffer == null) { 98 | return; 99 | } 100 | if (!line.isActive()) { 101 | line.start(); 102 | } 103 | float currVolume = volume * AudioEngine.getMasterVolume(); 104 | if (playbackVolume != currVolume) { 105 | playbackVolume = currVolume; 106 | float gainDB = (float) (20 * Math.log10(playbackVolume)); 107 | setControlValue(FloatControl.Type.MASTER_GAIN, gainDB); 108 | } 109 | if (playbackPan != pan) { 110 | playbackPan = pan; 111 | setControlValue(FloatControl.Type.PAN, pan); 112 | } 113 | 114 | if (playbackPos < buffer.data.length) { 115 | // Keep 32ms of data in the buffer 116 | final int maxFrames = (int)Math.ceil(format.getFrameRate() * 0.032f); 117 | final int maxLength = maxFrames * format.getFrameSize(); 118 | final int currLength = line.getBufferSize() - line.available(); 119 | if (currLength < maxLength) { 120 | final int length = Math.min(maxLength - currLength, buffer.data.length - playbackPos); 121 | final int bytesWritten = line.write(buffer.data, playbackPos, length); 122 | if (bytesWritten > 0) { 123 | playbackPos += bytesWritten; 124 | if (loop && playbackPos == buffer.data.length) { 125 | playbackPos = 0; 126 | } 127 | } 128 | } 129 | } 130 | if (playbackPos >= buffer.data.length) { 131 | // Fill entire buffer of blank data, which ensures the audio data is drained. 132 | int bytesToWrite = line.available(); 133 | if (blankData.length < bytesToWrite) { 134 | blankData = new byte[bytesToWrite]; 135 | } 136 | final int bytesWritten = line.write(blankData, 0, bytesToWrite); 137 | if (bytesWritten > 0) { 138 | playbackPos += bytesWritten; 139 | } 140 | 141 | if (playbackPos >= buffer.data.length + line.getBufferSize()) { 142 | buffer = null; 143 | line.stop(); 144 | line.flush(); 145 | } 146 | } 147 | } 148 | } 149 | 150 | private void setControlValue(FloatControl.Type type, float value) { 151 | FloatControl control = null; 152 | try { 153 | control = (FloatControl) line.getControl(type); 154 | } catch (Exception ex) { 155 | // Unsupported control type 156 | } 157 | if (control != null) { 158 | if (line.isActive()) { 159 | control.shift(control.getValue(), value, 50); 160 | } else { 161 | control.setValue(value); 162 | } 163 | } 164 | } 165 | 166 | // endregion 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * A simple app/game framework with a gameloop, scene graph, fixed-width bitmap fonts, and 3 | * resource manager. Sits on top of AWT. 4 | */ 5 | package com.brackeen.app; -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/view/Button.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.view; 2 | 3 | import java.awt.Cursor; 4 | import java.awt.Graphics2D; 5 | import java.awt.event.MouseEvent; 6 | import java.awt.event.MouseListener; 7 | import java.awt.image.BufferedImage; 8 | 9 | @SuppressWarnings("unused") 10 | public class Button extends View implements MouseListener { 11 | 12 | public interface Listener { 13 | void buttonClicked(Button button); 14 | } 15 | 16 | private enum State { 17 | NORMAL, 18 | HOVER, 19 | PRESSED, 20 | } 21 | 22 | private BufferedImage normalImage; 23 | private BufferedImage hoverImage; 24 | private BufferedImage pressedImage; 25 | private BufferedImage normalSelectedImage; 26 | private BufferedImage hoverSelectedImage; 27 | private BufferedImage pressedSelectedImage; 28 | private State state = State.NORMAL; 29 | private boolean selected = false; 30 | private boolean armed = false; 31 | private View rootWhenArmed; 32 | private Listener buttonListener; 33 | 34 | public Button(BufferedImage normalImage) { 35 | setNormalImage(normalImage); 36 | setMouseListener(this); 37 | setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 38 | sizeToFit(); 39 | } 40 | 41 | @Override 42 | public void sizeToFit() { 43 | BufferedImage image = getDisplayedImage(); 44 | if (image == null) { 45 | setSize(0, 0); 46 | } else { 47 | setSize(image.getWidth(), image.getHeight()); 48 | } 49 | } 50 | 51 | public Listener getButtonListener() { 52 | return buttonListener; 53 | } 54 | 55 | public void setButtonListener(Listener buttonListener) { 56 | this.buttonListener = buttonListener; 57 | } 58 | 59 | private void setState(State state) { 60 | this.state = state; 61 | } 62 | 63 | public BufferedImage getHoverImage() { 64 | return hoverImage; 65 | } 66 | 67 | public void setHoverImage(BufferedImage hoverImage) { 68 | this.hoverImage = hoverImage; 69 | } 70 | 71 | public BufferedImage getHoverSelectedImage() { 72 | return hoverSelectedImage; 73 | } 74 | 75 | public void setHoverSelectedImage(BufferedImage hoverSelectedImage) { 76 | this.hoverSelectedImage = hoverSelectedImage; 77 | } 78 | 79 | public boolean isSelected() { 80 | return selected; 81 | } 82 | 83 | public void setSelected(boolean isSelected) { 84 | this.selected = isSelected; 85 | } 86 | 87 | public BufferedImage getNormalImage() { 88 | return normalImage; 89 | } 90 | 91 | public void setNormalImage(BufferedImage normalImage) { 92 | this.normalImage = normalImage; 93 | } 94 | 95 | public BufferedImage getNormalSelectedImage() { 96 | return normalSelectedImage; 97 | } 98 | 99 | public void setNormalSelectedImage(BufferedImage normalSelectedImage) { 100 | this.normalSelectedImage = normalSelectedImage; 101 | } 102 | 103 | public BufferedImage getPressedImage() { 104 | return pressedImage; 105 | } 106 | 107 | public void setPressedImage(BufferedImage pressedImage) { 108 | this.pressedImage = pressedImage; 109 | } 110 | 111 | public BufferedImage getPressedSelectedImage() { 112 | return pressedSelectedImage; 113 | } 114 | 115 | public void setPressedSelectedImage(BufferedImage pressedSelectedImage) { 116 | this.pressedSelectedImage = pressedSelectedImage; 117 | } 118 | 119 | private BufferedImage getDisplayedImage() { 120 | if (selected) { 121 | BufferedImage defaultImage = normalSelectedImage; 122 | if (defaultImage == null) { 123 | defaultImage = normalImage; 124 | } 125 | if (state == State.NORMAL) { 126 | return defaultImage; 127 | } else if (state == State.HOVER) { 128 | if (hoverSelectedImage != null) { 129 | return hoverSelectedImage; 130 | } else { 131 | return defaultImage; 132 | } 133 | } else { 134 | if (pressedSelectedImage != null) { 135 | return pressedSelectedImage; 136 | } else { 137 | return defaultImage; 138 | } 139 | } 140 | } else { 141 | if (state == State.NORMAL) { 142 | return normalImage; 143 | } else if (state == State.HOVER) { 144 | if (hoverImage != null) { 145 | return hoverImage; 146 | } else { 147 | return normalImage; 148 | } 149 | } else { 150 | if (pressedImage != null) { 151 | return pressedImage; 152 | } else { 153 | return normalImage; 154 | } 155 | } 156 | } 157 | } 158 | 159 | @Override 160 | public void onDraw(Graphics2D g) { 161 | g.drawImage(getDisplayedImage(), null, null); 162 | } 163 | 164 | @Override 165 | public void mouseEntered(MouseEvent me) { 166 | if (armed && me.getID() == MouseEvent.MOUSE_DRAGGED) { 167 | setState(State.PRESSED); 168 | } else if (me.getID() == MouseEvent.MOUSE_MOVED || me.getID() == MouseEvent.MOUSE_ENTERED) { 169 | setState(State.HOVER); 170 | } 171 | } 172 | 173 | @Override 174 | public void mouseExited(MouseEvent me) { 175 | setState(State.NORMAL); 176 | } 177 | 178 | @Override 179 | public void mouseClicked(MouseEvent me) { 180 | // Do nothing - press+release events sent 181 | } 182 | 183 | @Override 184 | public void mousePressed(MouseEvent me) { 185 | setState(State.PRESSED); 186 | rootWhenArmed = getRoot(); 187 | armed = true; 188 | } 189 | 190 | @Override 191 | public void mouseReleased(MouseEvent me) { 192 | View root = getRoot(); 193 | View view = root.pick(me.getX(), me.getY()); 194 | boolean isOver = isAncestorOf(view); 195 | boolean isSameRoot = rootWhenArmed == root; 196 | boolean isTap = armed && state == State.PRESSED && isOver && isSameRoot; 197 | 198 | if (isOver) { 199 | setState(State.HOVER); 200 | } else { 201 | setState(State.NORMAL); 202 | } 203 | rootWhenArmed = null; 204 | armed = false; 205 | 206 | if (isTap && buttonListener != null) { 207 | buttonListener.buttonClicked(this); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/view/ImageView.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.view; 2 | 3 | import java.awt.Graphics2D; 4 | import java.awt.image.BufferedImage; 5 | 6 | @SuppressWarnings("unused") 7 | public class ImageView extends View { 8 | 9 | private BufferedImage image; 10 | 11 | public ImageView(BufferedImage image) { 12 | this.image = image; 13 | sizeToFit(); 14 | } 15 | 16 | public BufferedImage getImage() { 17 | return image; 18 | } 19 | 20 | public void setImage(BufferedImage image) { 21 | this.image = image; 22 | } 23 | 24 | @Override 25 | public void sizeToFit() { 26 | if (image == null) { 27 | setSize(0, 0); 28 | } else { 29 | setSize(image.getWidth(), image.getHeight()); 30 | } 31 | } 32 | 33 | @Override 34 | public void onDraw(Graphics2D g) { 35 | if (image != null) { 36 | g.drawImage(image, null, null); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/view/Label.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.view; 2 | 3 | import com.brackeen.app.BitmapFont; 4 | 5 | import java.awt.Graphics2D; 6 | 7 | @SuppressWarnings("unused") 8 | public class Label extends View { 9 | 10 | private BitmapFont font; 11 | private String text; 12 | 13 | public Label() { 14 | 15 | } 16 | 17 | public Label(BitmapFont font, String text) { 18 | this.font = font; 19 | this.text = text; 20 | sizeToFit(); 21 | } 22 | 23 | public BitmapFont getFont() { 24 | return font; 25 | } 26 | 27 | public void setFont(BitmapFont font) { 28 | this.font = font; 29 | } 30 | 31 | public String getText() { 32 | return text; 33 | } 34 | 35 | public void setText(String text) { 36 | this.text = text; 37 | } 38 | 39 | @Override 40 | public void sizeToFit() { 41 | if (font == null) { 42 | setSize(0, 0); 43 | } else { 44 | setSize(font.getStringWidth(text), font.getHeight()); 45 | } 46 | } 47 | 48 | @Override 49 | public void onDraw(Graphics2D g) { 50 | if (font != null) { 51 | font.drawString(g, text); 52 | } 53 | } 54 | 55 | public static View makeMultilineLabel(BitmapFont font, String text, float anchorX) { 56 | View view = new View(); 57 | int y = 0; 58 | int index = 0; 59 | while (true) { 60 | String line; 61 | int newIndex = text.indexOf('\n', index); 62 | if (newIndex == -1) { 63 | line = text.substring(index); 64 | } else { 65 | line = text.substring(index, newIndex); 66 | } 67 | Label label = new Label(font, line); 68 | label.setLocation(0, y); 69 | label.setAnchor(anchorX, 0); 70 | view.addSubview(label); 71 | y += font.getHeight(); 72 | 73 | if (newIndex == -1) { 74 | break; 75 | } 76 | index = newIndex + 1; 77 | } 78 | view.setHeight(y); 79 | return view; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/view/Scene.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.view; 2 | 3 | @SuppressWarnings("unused") 4 | public class Scene extends View { 5 | 6 | private View focusedView = this; 7 | 8 | public View getFocusedView() { 9 | return focusedView; 10 | } 11 | 12 | public void setFocusedView(View focusedView) { 13 | this.focusedView = focusedView; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/app/view/View.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.app.view; 2 | 3 | import com.brackeen.app.App; 4 | 5 | import java.awt.AlphaComposite; 6 | import java.awt.Color; 7 | import java.awt.Composite; 8 | import java.awt.Cursor; 9 | import java.awt.Graphics2D; 10 | import java.awt.event.FocusListener; 11 | import java.awt.event.KeyListener; 12 | import java.awt.event.MouseListener; 13 | import java.awt.event.MouseMotionListener; 14 | import java.awt.geom.AffineTransform; 15 | import java.awt.geom.NoninvertibleTransformException; 16 | import java.awt.geom.Point2D; 17 | import java.awt.geom.Rectangle2D; 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | @SuppressWarnings("unused") 23 | public class View { 24 | 25 | private View superview = null; 26 | private final List subviews = new ArrayList<>(); 27 | private Color backgroundColor = null; 28 | private float x = 0; 29 | private float y = 0; 30 | private float width = 0; 31 | private float height = 0; 32 | private float anchorX = 0; 33 | private float anchorY = 0; 34 | private float opacity = 1; 35 | private boolean visible = true; 36 | private boolean enabled = true; 37 | private boolean loaded = false; 38 | 39 | private MouseListener mouseListener; 40 | private MouseMotionListener mouseMotionListener; 41 | private KeyListener keyListener; 42 | private FocusListener focusListener; 43 | private Cursor cursor; 44 | 45 | private final AffineTransform worldTransform = new AffineTransform(); 46 | private final AffineTransform drawTransform = new AffineTransform(); 47 | private boolean localTransformDirty = true; 48 | private long localTransformModCount = 0; 49 | private long superviewTransformModCount = 0; 50 | 51 | public View() { 52 | 53 | } 54 | 55 | public View(float x, float y, float width, float height) { 56 | setLocation(x, y); 57 | setSize(width, height); 58 | } 59 | 60 | // Input 61 | 62 | public boolean isEnabled() { 63 | return enabled; 64 | } 65 | 66 | public void setEnabled(boolean enabled) { 67 | this.enabled = enabled; 68 | } 69 | 70 | public Cursor getCursor() { 71 | if (cursor == null) { 72 | if (superview != null) { 73 | return superview.getCursor(); 74 | } else { 75 | return Cursor.getDefaultCursor(); 76 | } 77 | } else { 78 | return cursor; 79 | } 80 | } 81 | 82 | public void setCursor(Cursor cursor) { 83 | this.cursor = cursor; 84 | } 85 | 86 | public KeyListener getKeyListener() { 87 | return keyListener; 88 | } 89 | 90 | public void setKeyListener(KeyListener keyListener) { 91 | this.keyListener = keyListener; 92 | } 93 | 94 | public MouseListener getMouseListener() { 95 | return mouseListener; 96 | } 97 | 98 | public void setMouseListener(MouseListener mouseListener) { 99 | this.mouseListener = mouseListener; 100 | } 101 | 102 | public MouseMotionListener getMouseMotionListener() { 103 | return mouseMotionListener; 104 | } 105 | 106 | public void setMouseMotionListener(MouseMotionListener mouseMotionListener) { 107 | this.mouseMotionListener = mouseMotionListener; 108 | } 109 | 110 | public FocusListener getFocusListener() { 111 | return focusListener; 112 | } 113 | 114 | public void setFocusListener(FocusListener focusListener) { 115 | this.focusListener = focusListener; 116 | } 117 | 118 | // Dimensions 119 | 120 | public float getX() { 121 | return this.x; 122 | } 123 | 124 | public void setX(float x) { 125 | this.x = x; 126 | localTransformDirty = true; 127 | } 128 | 129 | public float getY() { 130 | return this.y; 131 | } 132 | 133 | public void setY(float y) { 134 | this.y = y; 135 | localTransformDirty = true; 136 | } 137 | 138 | public void setLocation(float x, float y) { 139 | setX(x); 140 | setY(y); 141 | } 142 | 143 | public float getAnchorX() { 144 | return anchorX; 145 | } 146 | 147 | public void setAnchorX(float anchorX) { 148 | this.anchorX = anchorX; 149 | localTransformDirty = true; 150 | } 151 | 152 | public float getAnchorY() { 153 | return anchorY; 154 | } 155 | 156 | public void setAnchorY(float anchorY) { 157 | this.anchorY = anchorY; 158 | localTransformDirty = true; 159 | } 160 | 161 | public void setAnchor(float anchorX, float anchorY) { 162 | setAnchorX(anchorX); 163 | setAnchorY(anchorY); 164 | } 165 | 166 | public float getWidth() { 167 | return this.width; 168 | } 169 | 170 | public void setWidth(float width) { 171 | if (this.width != width) { 172 | this.width = width; 173 | localTransformDirty = true; 174 | if (loaded) { 175 | onResize(); 176 | } 177 | } 178 | } 179 | 180 | public float getHeight() { 181 | return this.height; 182 | } 183 | 184 | public void setHeight(float height) { 185 | if (this.height != height) { 186 | this.height = height; 187 | localTransformDirty = true; 188 | if (loaded) { 189 | onResize(); 190 | } 191 | } 192 | } 193 | 194 | public void setSize(float width, float height) { 195 | if (this.width != width || this.height != height) { 196 | this.width = width; 197 | this.height = height; 198 | localTransformDirty = true; 199 | if (loaded) { 200 | onResize(); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Sizes this View to fit it's contents. Subclasses may use this. Does nothing by default. 207 | */ 208 | public void sizeToFit() { 209 | // Do nothing 210 | } 211 | 212 | // Color 213 | 214 | public Color getBackgroundColor() { 215 | return backgroundColor; 216 | } 217 | 218 | public void setBackgroundColor(Color backgroundColor) { 219 | this.backgroundColor = backgroundColor; 220 | } 221 | 222 | public float getOpacity() { 223 | return opacity; 224 | } 225 | 226 | public void setOpacity(float opacity) { 227 | this.opacity = opacity; 228 | } 229 | 230 | public boolean isVisible() { 231 | return visible; 232 | } 233 | 234 | public void setVisible(boolean visible) { 235 | this.visible = visible; 236 | } 237 | 238 | // Subviews and superviews 239 | 240 | public View getSuperview() { 241 | return superview; 242 | } 243 | 244 | public View getRoot() { 245 | View root = this; 246 | while (root.superview != null) { 247 | root = root.superview; 248 | } 249 | return root; 250 | } 251 | 252 | public boolean isAncestorOf(View view) { 253 | while (view != null) { 254 | if (view == this) { 255 | return true; 256 | } else { 257 | view = view.getSuperview(); 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | public void removeFromSuperview() { 264 | if (superview != null) { 265 | superview.subviews.remove(this); 266 | superview = null; 267 | } 268 | } 269 | 270 | public List getSubviews() { 271 | return Collections.unmodifiableList(subviews); 272 | } 273 | 274 | public void removeAllSubviews() { 275 | for (View subview : new ArrayList<>(subviews)) { 276 | subview.removeFromSuperview(); 277 | } 278 | subviews.clear(); 279 | } 280 | 281 | public void addSubview(View subview) { 282 | addSubview(subview, subviews.size()); 283 | } 284 | 285 | public void addSubview(View subview, int index) { 286 | if (index < 0) { 287 | index = 0; 288 | } else if (index > subviews.size()) { 289 | index = subviews.size(); 290 | } 291 | subview.removeFromSuperview(); 292 | subviews.add(index, subview); 293 | subview.superview = this; 294 | superviewTransformModCount = -1; 295 | } 296 | 297 | public int indexOfSubview(View subview) { 298 | for (int i = 0; i < subviews.size(); i++) { 299 | if (subview == subviews.get(i)) { 300 | return i; 301 | } 302 | } 303 | return -1; 304 | } 305 | 306 | public void notifySuperviewDirty() { 307 | superviewTransformModCount = -1; 308 | } 309 | 310 | // Notifications 311 | 312 | public final void load() { 313 | onLoad(); 314 | loaded = true; 315 | for (View subview : subviews) { 316 | subview.load(); 317 | } 318 | } 319 | 320 | public void onLoad() { 321 | 322 | } 323 | 324 | public final void unload() { 325 | loaded = false; 326 | onUnload(); 327 | for (View subview : subviews) { 328 | subview.unload(); 329 | } 330 | } 331 | 332 | public void onUnload() { 333 | 334 | } 335 | 336 | // Only invoked if the View has been loaded (after onLoad(), before onUnload()) 337 | public void onResize() { 338 | 339 | } 340 | 341 | public final void tick() { 342 | onTick(); 343 | for (View subview : subviews) { 344 | subview.tick(); 345 | } 346 | } 347 | 348 | public void onTick() { 349 | 350 | } 351 | 352 | // Transforms 353 | 354 | private void updateTransforms() { 355 | boolean worldTransformDirty = false; 356 | 357 | if (superview == null) { 358 | if (localTransformDirty || superviewTransformModCount != 0) { 359 | worldTransform.setToIdentity(); 360 | worldTransform.scale(App.getApp().getPixelScale(), App.getApp().getPixelScale()); 361 | drawTransform.setToIdentity(); 362 | superviewTransformModCount = 0; 363 | worldTransformDirty = true; 364 | } 365 | } else { 366 | superview.updateTransforms(); 367 | if (localTransformDirty || superviewTransformModCount != superview.localTransformModCount) { 368 | worldTransform.setTransform(superview.worldTransform); 369 | drawTransform.setTransform(superview.drawTransform); 370 | superviewTransformModCount = superview.localTransformModCount; 371 | worldTransformDirty = true; 372 | } 373 | } 374 | 375 | if (worldTransformDirty) { 376 | worldTransform.translate(x, y); 377 | drawTransform.translate(x, y); 378 | 379 | if (anchorX != 0 || anchorY != 0) { 380 | float anchorLocalX = anchorX * getWidth(); 381 | float anchorLocalY = anchorY * getHeight(); 382 | worldTransform.translate(-anchorLocalX, -anchorLocalY); 383 | drawTransform.translate(-anchorLocalX, -anchorLocalY); 384 | } 385 | 386 | localTransformModCount++; 387 | localTransformDirty = false; 388 | } 389 | } 390 | 391 | public boolean isClippedToBounds() { 392 | return false; 393 | } 394 | 395 | public Point2D.Float getLocalLocation(float worldX, float worldY) { 396 | updateTransforms(); 397 | Point2D.Float src = new Point2D.Float(worldX, worldY); 398 | Point2D.Float dst = new Point2D.Float(0, 0); 399 | try { 400 | worldTransform.inverseTransform(src, dst); 401 | return dst; 402 | } catch (NoninvertibleTransformException ex) { 403 | return null; 404 | } 405 | } 406 | 407 | public boolean contains(float worldX, float worldY) { 408 | Point2D.Float local = getLocalLocation(worldX, worldY); 409 | return (local != null && local.x >= 0 && local.x < getWidth() && local.y >= 0 && local.y < getHeight()); 410 | } 411 | 412 | public View pick(float worldX, float worldY) { 413 | return pick(worldX, worldY, false); 414 | } 415 | 416 | public View pick(float worldX, float worldY, boolean allowDisabledViews) { 417 | if (!isVisible() || getOpacity() <= 0) { 418 | return null; 419 | } else { 420 | boolean inside = contains(worldX, worldY); 421 | if (isClippedToBounds() && !inside) { 422 | return null; 423 | } 424 | for (int i = subviews.size() - 1; i >= 0; i--) { 425 | View pickedView = subviews.get(i).pick(worldX, worldY, allowDisabledViews); 426 | if (pickedView != null) { 427 | return pickedView; 428 | } 429 | } 430 | 431 | boolean isPick = inside && (allowDisabledViews || isEnabled()); 432 | 433 | return isPick ? this : null; 434 | } 435 | } 436 | 437 | // Drawing 438 | 439 | public final void draw(Graphics2D g) { 440 | if (!visible || opacity <= 0) { 441 | return; 442 | } 443 | 444 | updateTransforms(); 445 | 446 | Composite oldComposite = null; 447 | 448 | if (opacity < 1) { 449 | oldComposite = g.getComposite(); 450 | if (oldComposite instanceof AlphaComposite) { 451 | AlphaComposite ac = (AlphaComposite) oldComposite; 452 | g.setComposite(ac.derive(opacity * ac.getAlpha())); 453 | } 454 | } 455 | 456 | g.setTransform(drawTransform); 457 | if (backgroundColor != null) { 458 | g.setColor(backgroundColor); 459 | g.fill(new Rectangle2D.Float(0, 0, getWidth(), getHeight())); 460 | } 461 | onDraw(g); 462 | 463 | for (View subview : subviews) { 464 | subview.draw(g); 465 | } 466 | 467 | if (oldComposite != null) { 468 | g.setComposite(oldComposite); 469 | } 470 | } 471 | 472 | /** 473 | * Draws the view. The background of the View, if any, is already drawn. The Graphics transform is 474 | * already set, and there's no need to offset the rendering. This View's subviews, if any, 475 | * are drawn afterwards. 476 | */ 477 | public void onDraw(Graphics2D g) { 478 | 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/BaseConsoleScene.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.app.BitmapFont; 5 | import com.brackeen.app.view.Label; 6 | import com.brackeen.app.view.Scene; 7 | import com.brackeen.app.view.View; 8 | 9 | import java.awt.Color; 10 | import java.util.List; 11 | 12 | public class BaseConsoleScene extends Scene { 13 | 14 | private static final int BORDER_SIZE = 0; 15 | 16 | protected BitmapFont messageFont; 17 | protected View textView; 18 | 19 | @Override 20 | public void onLoad() { 21 | App app = App.getApp(); 22 | 23 | messageFont = new BitmapFont(app.getImage("/ui/console_font.png"), 8, ' '); 24 | messageFont.setTracking(-2); 25 | 26 | setBackgroundColor(new Color(12, 12, 12)); 27 | 28 | textView = new View(); 29 | textView.setLocation(BORDER_SIZE, BORDER_SIZE); 30 | addSubview(textView); 31 | } 32 | 33 | @Override 34 | public void onResize() { 35 | textView.setSize(getWidth() - BORDER_SIZE * 2, getHeight() - BORDER_SIZE * 2); 36 | 37 | textView.removeAllSubviews(); 38 | int maxLines = (int) textView.getHeight() / messageFont.getHeight(); 39 | for (int i = 0; i < maxLines; i++) { 40 | Label label = new Label(messageFont, ""); 41 | label.setLocation(0, i * messageFont.getHeight()); 42 | textView.addSubview(label); 43 | } 44 | } 45 | 46 | @Override 47 | public void onTick() { 48 | setupTextViews(""); 49 | } 50 | 51 | protected void setupTextViews(String lastLine) { 52 | List log = App.getApp().getLog(); 53 | List labels = textView.getSubviews(); 54 | int numLogLines = Math.min(log.size(), labels.size() - 1); 55 | for (int i = 0; i < labels.size(); i++) { 56 | Label label = (Label) labels.get(i); 57 | if (i < numLogLines) { 58 | label.setText(log.get(log.size() - numLogLines + i)); 59 | } else if (i == numLogLines) { 60 | label.setText(lastLine); 61 | } else { 62 | label.setText(""); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/CollisionDetection.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.scared.entity.Entity; 4 | import com.brackeen.scared.entity.Player; 5 | 6 | public class CollisionDetection { 7 | 8 | private static final int WALL_COLLISION_NONE = 0; 9 | private static final int WALL_COLLISION_NORTH = 1; 10 | private static final int WALL_COLLISION_SOUTH = 2; 11 | private static final int WALL_COLLISION_WEST = 4; 12 | private static final int WALL_COLLISION_EAST = 8; 13 | 14 | private static final float SLIDE_ERROR = 0.00002f; 15 | 16 | private final Map map; 17 | 18 | public CollisionDetection(Map map) { 19 | this.map = map; 20 | } 21 | 22 | public void move(Entity entity, float newX, float newY, boolean collideWithEntities, boolean collideWithWalls) { 23 | float oldX = entity.getX(); 24 | float oldY = entity.getY(); 25 | if (oldX == newX && oldY == newY) { 26 | return; 27 | } 28 | 29 | Tile oldTile = map.getTileAt(entity); 30 | entity.setLocation(newX, newY); 31 | 32 | if (entity.getRadius() > 0) { 33 | if (collideWithEntities) { 34 | detectAndHandleEntityCollisions(entity, oldX, oldY); 35 | } 36 | 37 | if (collideWithWalls) { 38 | detectAndHandleWallCollisions(entity, oldX, oldY); 39 | } 40 | } 41 | 42 | Tile newTile = map.getTileAt(entity); 43 | if (oldTile != newTile) { 44 | if (oldTile != null) { 45 | oldTile.removeEntity(entity); 46 | } 47 | if (newTile != null) { 48 | newTile.addEntity(entity); 49 | } 50 | } 51 | } 52 | 53 | // 54 | // Entity collisions 55 | // 56 | 57 | private void detectAndHandleEntityCollisions(Entity movingEntity, float oldX, float oldY) { 58 | Entity[] collidingEntities = checkEntityCollision(movingEntity, oldX, oldY); 59 | 60 | // Handle object collision 61 | if (collidingEntities == null || collidingEntities.length == 0) { 62 | return; 63 | } 64 | if (collidingEntities.length == 1) { 65 | 66 | Entity collidingEntity = collidingEntities[0]; 67 | 68 | if (movingEntity.onCollisionWithEntityShouldSlide()) { 69 | float collisionDx = movingEntity.getX() - collidingEntity.getX(); 70 | float collisionDy = movingEntity.getY() - collidingEntity.getY(); 71 | 72 | float actualDistanceSq = collisionDx * collisionDx + collisionDy * collisionDy; 73 | float actualDistance = (float) Math.sqrt(actualDistanceSq); 74 | 75 | if (actualDistance <= 0) { 76 | movingEntity.setLocation(oldX, oldY); 77 | } else { 78 | float goalDistance = movingEntity.getRadius() + collidingEntity.getRadius(); 79 | 80 | movingEntity.setX(collidingEntity.getX() + collisionDx * goalDistance / actualDistance); 81 | movingEntity.setY(collidingEntity.getY() + collisionDy * goalDistance / actualDistance); 82 | } 83 | } else { 84 | // Circle-Line Intersection 85 | float angle = (float) Math.atan2(movingEntity.getY() - oldY, movingEntity.getX() - oldX); 86 | float cos = (float) Math.cos(angle); 87 | float sin = (float) Math.sin(angle); 88 | 89 | float x = collidingEntity.getX() - oldX; 90 | float y = collidingEntity.getY() - oldY; 91 | float u = x * cos + y * sin; 92 | float D = y * cos - x * sin; 93 | 94 | float error = 0.005f; // I chose this value at random 95 | float r = movingEntity.getRadius() + collidingEntity.getRadius() + error; 96 | float discriminant = r * r - D * D; 97 | 98 | if (discriminant < 0) { 99 | movingEntity.setLocation(oldX, oldY); 100 | } else { 101 | float d = (float) Math.sqrt(discriminant); 102 | movingEntity.setLocation(oldX + (u - d) * cos, oldY + (u - d) * sin); 103 | } 104 | } 105 | } else { // collidingObjects.length == 2 106 | // From "Intersection of two circles" by Paul Bourke 107 | // http://astronomy.swin.edu.au/~pbourke/geometry/2circle/ 108 | 109 | Entity P0 = collidingEntities[0]; 110 | Entity P1 = collidingEntities[1]; 111 | 112 | float dx = P1.getX() - P0.getX(); 113 | float dy = P1.getY() - P0.getY(); 114 | 115 | float r0 = P0.getRadius() + movingEntity.getRadius(); 116 | float r1 = P1.getRadius() + movingEntity.getRadius(); 117 | 118 | float r0Squared = r0 * r0; 119 | float r1Squared = r1 * r1; 120 | 121 | float dSquared = dx * dx + dy * dy; 122 | 123 | if (dSquared <= 0) { 124 | movingEntity.setLocation(oldX, oldY); 125 | return; 126 | } 127 | 128 | float d = (float) Math.sqrt(dSquared); 129 | 130 | float a = (r0Squared - r1Squared + dSquared) / (2 * d); 131 | float aSquared = a * a; 132 | 133 | float hSquared = r0Squared - aSquared; 134 | 135 | if (hSquared <= 0) { 136 | movingEntity.setLocation(oldX, oldY); 137 | return; 138 | } 139 | 140 | float h = (float) Math.sqrt(hSquared); 141 | 142 | float midX = P0.getX() + a * dx / d; 143 | float midY = P0.getY() + a * dy / d; 144 | 145 | float sx = h * dy / d; 146 | float sy = h * dx / d; 147 | 148 | // two solutions - use the solution closest to original location. 149 | float sx1 = midX + sx; 150 | float sy1 = midY - sy; 151 | 152 | float sx2 = midX - sx; 153 | float sy2 = midY + sy; 154 | 155 | float d1 = (sx1 - oldX) * (sx1 - oldX) + (sy1 - oldY) * (sy1 - oldY); 156 | float d2 = (sx2 - oldX) * (sx2 - oldX) + (sy2 - oldY) * (sy2 - oldY); 157 | 158 | if (d1 < d2) { 159 | movingEntity.setLocation(sx1, sy1); 160 | } else if (d1 > d2) { 161 | movingEntity.setLocation(sx2, sy2); 162 | } else { 163 | movingEntity.setLocation(oldX, oldY); 164 | } 165 | } 166 | } 167 | 168 | /** 169 | * Checks the object's path for any collisions. 170 | */ 171 | private Entity[] checkEntityCollision(Entity movingEntity, float oldX, float oldY) { 172 | float newX = movingEntity.getX(); 173 | float newY = movingEntity.getY(); 174 | 175 | float dx = newX - oldX; 176 | float dy = newY - oldY; 177 | 178 | float distanceSquared = dx * dx + dy * dy; 179 | 180 | // the shorter the maxDistance, the more accurate it'll be. 181 | float maxDistance = movingEntity.getRadius(); 182 | float maxDistanceSquared = maxDistance * maxDistance; 183 | 184 | if (distanceSquared > maxDistanceSquared) { 185 | 186 | // follow the path 187 | int steps = (int) (Math.sqrt(distanceSquared) / maxDistance); 188 | 189 | for (int i = 1; i <= steps; i++) { 190 | movingEntity.setLocation(oldX + i * dx / steps, oldY + i * dy / steps); 191 | 192 | Entity[] entities = checkEntityCollisionAtPoint(movingEntity); 193 | 194 | if (entities != null) { 195 | return entities; 196 | } 197 | } 198 | 199 | movingEntity.setLocation(newX, newY); 200 | } 201 | 202 | return checkEntityCollisionAtPoint(movingEntity); 203 | } 204 | 205 | /** 206 | * Returns up to two entity collisions of the moving entity at it's current location. 207 | */ 208 | private Entity[] checkEntityCollisionAtPoint(Entity movingEntity) { 209 | int tileX = (int) movingEntity.getX(); 210 | int tileY = (int) movingEntity.getY(); 211 | 212 | Entity closestEntity = null; 213 | Entity secondClosestEntity = null; 214 | float closestDist = Float.MAX_VALUE; 215 | float secondClosestDist = Float.MAX_VALUE; 216 | 217 | // check this tile and 8 surrounding tiles 218 | for (int x = tileX - 1; x <= tileX + 1; x++) { 219 | 220 | if (movingEntity.isDeleted()) { 221 | break; 222 | } 223 | 224 | for (int y = tileY - 1; y <= tileY + 1; y++) { 225 | 226 | if (movingEntity.isDeleted()) { 227 | break; 228 | } 229 | 230 | Tile tile = map.getTileAt(x, y); 231 | 232 | if (tile == null || !tile.hasEntities()) { 233 | continue; 234 | } 235 | 236 | for (Entity entity : tile.getEntities()) { 237 | 238 | if (movingEntity.isDeleted()) { 239 | break; 240 | } 241 | 242 | if (movingEntity == entity || entity.isDeleted() || entity.getRadius() <= 0) { 243 | continue; 244 | } 245 | 246 | float dx = entity.getX() - movingEntity.getX(); 247 | float dy = entity.getY() - movingEntity.getY(); 248 | 249 | // Don't collide with entities in exact same location 250 | if (dx == 0 && dy == 0) { 251 | continue; 252 | } 253 | 254 | float r = movingEntity.getRadius() + entity.getRadius(); 255 | float r2 = r * r; 256 | 257 | float d2 = dx * dx + dy * dy; 258 | 259 | if (d2 < r2) { 260 | // collision found 261 | boolean handleCollision = entity.notifyCollision(movingEntity); 262 | 263 | if (handleCollision) { 264 | if (closestEntity == null) { 265 | closestEntity = entity; 266 | closestDist = d2; 267 | } else if (d2 < closestDist) { 268 | secondClosestEntity = closestEntity; 269 | secondClosestDist = closestDist; 270 | closestEntity = entity; 271 | closestDist = d2; 272 | } else if (secondClosestEntity == null || d2 < secondClosestDist) { 273 | secondClosestEntity = entity; 274 | secondClosestDist = d2; 275 | } 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | if (closestEntity == null) { 283 | return null; 284 | } else if (secondClosestEntity == null) { 285 | return new Entity[]{closestEntity}; 286 | } else { 287 | return new Entity[]{closestEntity, secondClosestEntity}; 288 | } 289 | } 290 | 291 | // 292 | // Wall collisions 293 | // 294 | 295 | private void detectAndHandleWallCollisions(Entity entity, float oldX, float oldY) { 296 | boolean isPlayer = (entity instanceof Player); 297 | 298 | int collision = checkWallCollision(oldX, oldY, entity.getX(), entity.getY(), entity.getRadius(), isPlayer); 299 | if (collision == WALL_COLLISION_NONE) { 300 | if (isPlayer) { 301 | map.notifyPlayerTouchedNoWall(); 302 | } 303 | } else if (entity.onCollisionWithWallShouldSlide()) { 304 | float altX = entity.getX(); 305 | float altY = entity.getY(); 306 | boolean collisionX = false; 307 | boolean collisionY = false; 308 | 309 | if ((collision & WALL_COLLISION_WEST) != 0) { 310 | altX = (float) Math.floor(oldX) + entity.getRadius() + SLIDE_ERROR; 311 | collisionX = true; 312 | } else if ((collision & WALL_COLLISION_EAST) != 0) { 313 | altX = (float) Math.ceil(oldX) - entity.getRadius() - SLIDE_ERROR; 314 | collisionX = true; 315 | } 316 | 317 | if ((collision & WALL_COLLISION_NORTH) != 0) { 318 | altY = (float) Math.floor(oldY) + entity.getRadius() + SLIDE_ERROR; 319 | collisionY = true; 320 | } else if ((collision & WALL_COLLISION_SOUTH) != 0) { 321 | altY = (float) Math.ceil(oldY) - entity.getRadius() - SLIDE_ERROR; 322 | collisionY = true; 323 | } 324 | 325 | if (collisionX && collisionY) { 326 | if (checkWallCollision(oldX, oldY, altX, entity.getY(), entity.getRadius(), isPlayer) == WALL_COLLISION_NONE) { 327 | altY = entity.getY(); 328 | } else if (checkWallCollision(oldX, oldY, entity.getX(), altY, entity.getRadius(), isPlayer) == WALL_COLLISION_NONE) { 329 | altX = entity.getX(); 330 | } 331 | } 332 | 333 | entity.setLocation(altX, altY); 334 | } else { 335 | float newX = entity.getX(); 336 | float newY = entity.getY(); 337 | 338 | float cX1 = 0; 339 | float cY1 = 0; 340 | float cX2 = 0; 341 | float cY2 = 0; 342 | boolean collisionX = false; 343 | boolean collisionY = false; 344 | 345 | if ((collision & WALL_COLLISION_WEST) != 0) { 346 | cX1 = (float) Math.floor(oldX) + entity.getRadius() + SLIDE_ERROR; 347 | cY1 = oldY + (cX1 - oldX) * (newY - oldY) / (newX - oldX); 348 | if (!map.isSolidAt((int) cX1, (int) cY1)) { 349 | collisionX = true; 350 | } 351 | 352 | } else if ((collision & WALL_COLLISION_EAST) != 0) { 353 | cX1 = (float) Math.ceil(oldX) - entity.getRadius() - SLIDE_ERROR; 354 | cY1 = oldY + (cX1 - oldX) * (newY - oldY) / (newX - oldX); 355 | if (!map.isSolidAt((int) cX1, (int) cY1)) { 356 | collisionX = true; 357 | } 358 | } 359 | 360 | if ((collision & WALL_COLLISION_NORTH) != 0) { 361 | cY2 = (float) Math.floor(oldY) + entity.getRadius() + SLIDE_ERROR; 362 | cX2 = oldX + (cY2 - oldY) * (newX - oldX) / (newY - oldY); 363 | if (!map.isSolidAt((int) cX2, (int) cY2)) { 364 | collisionY = true; 365 | } 366 | } else if ((collision & WALL_COLLISION_SOUTH) != 0) { 367 | cY2 = (float) Math.ceil(oldY) - entity.getRadius() - SLIDE_ERROR; 368 | cX2 = oldX + (cY2 - oldY) * (newX - oldX) / (newY - oldY); 369 | if (!map.isSolidAt((int) cX2, (int) cY2)) { 370 | collisionY = true; 371 | } 372 | } 373 | 374 | if (collisionX && collisionY) { 375 | // if more than one collision, use the collision closest to the old position 376 | float dist1Sq = (cX1 - oldX) * (cX1 - oldX) + (cY1 - oldY) * (cY1 - oldY); 377 | float dist2Sq = (cX2 - oldX) * (cX2 - oldX) + (cY2 - oldY) * (cY2 - oldY); 378 | 379 | if (dist1Sq <= dist2Sq) { 380 | newX = cX1; 381 | newY = cY1; 382 | } else { 383 | newX = cX2; 384 | newY = cY2; 385 | } 386 | } else if (collisionX) { 387 | newX = cX1; 388 | newY = cY1; 389 | } else if (collisionY) { 390 | newX = cX2; 391 | newY = cY2; 392 | } else { 393 | // no good "stop" result - go back to old location 394 | entity.setLocation(oldX, oldY); 395 | return; 396 | } 397 | 398 | entity.setLocation(newX, newY); 399 | } 400 | } 401 | 402 | 403 | /** 404 | * Returns integer with flags WALL_COLLISION_WEST, WALL_COLLISION_EAST, 405 | * WALL_COLLISION_NORTH, and/or WALL_COLLISION_SOUTH; 406 | * or returns WALL_COLLISION_NONE if there is no wall collision. 407 | */ 408 | private int checkWallCollision(float oldX, float oldY, float x, float y, float radius, 409 | boolean isPlayer) { 410 | int originTileX = (int) oldX; 411 | int originTileY = (int) oldY; 412 | 413 | int x1 = (int) (x - radius); 414 | int y1 = (int) (y - radius); 415 | int x2 = (int) (x + radius); 416 | int y2 = (int) (y + radius); 417 | 418 | int collision = 0; 419 | 420 | // check for solid walls 421 | for (int tileY = y1; tileY <= y2; tileY++) { 422 | for (int tileX = x1; tileX <= x2; tileX++) { 423 | Tile tile = map.getTileAt(tileX, tileY); 424 | 425 | // Treat out-of-bounds tiles as "solid" 426 | if (tile != null && !tile.isSolid()) { 427 | continue; 428 | } 429 | 430 | if (tileX < originTileX && x < oldX) { 431 | collision |= WALL_COLLISION_WEST; 432 | } else if (tileX > originTileX && x > oldX) { 433 | collision |= WALL_COLLISION_EAST; 434 | } 435 | 436 | if (tileY < originTileY && y < oldY) { 437 | collision |= WALL_COLLISION_NORTH; 438 | } else if (tileY > originTileY && y > oldY) { 439 | collision |= WALL_COLLISION_SOUTH; 440 | } 441 | 442 | if (isPlayer && tile != null && tile.renderState == 0) { 443 | map.notifyPlayerTouchedWall(tile, tileX, tileY); 444 | } 445 | } 446 | } 447 | 448 | return collision; 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/ConsoleScene.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | 5 | import java.awt.event.KeyEvent; 6 | import java.awt.event.KeyListener; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class ConsoleScene extends BaseConsoleScene { 11 | 12 | private static final int CURSOR_BLINK_TICKS = 20; 13 | private static final int MAX_COMMAND_HISTORY = 200; 14 | private static final String PROMPT = "] "; 15 | 16 | private static List originalCommandHistory = new ArrayList<>(); 17 | private static List editedCommandHistory = new ArrayList<>(); 18 | 19 | private final GameScene gameScene; 20 | private String newCommandLine = ""; 21 | private int commandHistoryIndex; 22 | private int ticks = 0; 23 | private boolean cursorOn = true; 24 | 25 | public ConsoleScene(GameScene gameScene) { 26 | this.gameScene = gameScene; 27 | } 28 | 29 | @Override 30 | public void onLoad() { 31 | super.onLoad(); 32 | 33 | commandHistoryIndex = originalCommandHistory.size(); 34 | 35 | onResize(); 36 | 37 | setKeyListener(new KeyListener() { 38 | 39 | @Override 40 | public void keyTyped(KeyEvent ke) { 41 | char ch = ke.getKeyChar(); 42 | if (messageFont.canDisplay(ch)) { 43 | setCurrentLine(getCurrentLine() + ch); 44 | } 45 | setCursorOn(true); 46 | } 47 | 48 | @Override 49 | public void keyPressed(KeyEvent ke) { 50 | if (ke.getKeyCode() == KeyEvent.VK_ESCAPE) { 51 | App.getApp().popScene(); 52 | } else if (ke.getKeyCode() == KeyEvent.VK_BACK_SPACE) { 53 | String currentLine = getCurrentLine(); 54 | if (currentLine.length() > 0) { 55 | setCurrentLine(currentLine.substring(0, currentLine.length() - 1)); 56 | setCursorOn(true); 57 | } 58 | } else if (ke.getKeyCode() == KeyEvent.VK_UP) { 59 | commandHistoryIndex = Math.max(0, commandHistoryIndex - 1); 60 | setCursorOn(true); 61 | } else if (ke.getKeyCode() == KeyEvent.VK_DOWN) { 62 | commandHistoryIndex = Math.min(originalCommandHistory.size(), commandHistoryIndex + 1); 63 | setCursorOn(true); 64 | } else if (ke.getKeyCode() == KeyEvent.VK_ENTER) { 65 | setCursorOn(true); 66 | String currentLine = getCurrentLine(); 67 | App.log(PROMPT + currentLine); 68 | if (currentLine.length() > 0) { 69 | String response = gameScene.doCommand(currentLine); 70 | if (response != null) { 71 | App.log(response); 72 | } 73 | 74 | setCurrentLine(getCurrentLine(originalCommandHistory)); 75 | 76 | originalCommandHistory.add(currentLine); 77 | editedCommandHistory.add(currentLine); 78 | if (originalCommandHistory.size() > MAX_COMMAND_HISTORY) { 79 | originalCommandHistory.remove(0); 80 | editedCommandHistory.remove(0); 81 | } 82 | commandHistoryIndex = originalCommandHistory.size(); 83 | newCommandLine = ""; 84 | } 85 | } 86 | } 87 | 88 | @Override 89 | public void keyReleased(KeyEvent ke) { 90 | 91 | } 92 | }); 93 | } 94 | 95 | private void setCursorOn(boolean cursorOn) { 96 | this.cursorOn = cursorOn; 97 | ticks = 0; 98 | } 99 | 100 | private String getCurrentLine() { 101 | return getCurrentLine(editedCommandHistory); 102 | } 103 | 104 | private String getCurrentLine(List commandHistory) { 105 | String currentLine; 106 | if (commandHistoryIndex < commandHistory.size()) { 107 | currentLine = commandHistory.get(commandHistoryIndex); 108 | } else { 109 | currentLine = newCommandLine; 110 | } 111 | return currentLine; 112 | } 113 | 114 | private void setCurrentLine(String currentLine) { 115 | if (commandHistoryIndex < editedCommandHistory.size()) { 116 | editedCommandHistory.set(commandHistoryIndex, currentLine); 117 | } else { 118 | newCommandLine = currentLine; 119 | } 120 | } 121 | 122 | @Override 123 | public void onTick() { 124 | ticks++; 125 | if (ticks >= CURSOR_BLINK_TICKS) { 126 | setCursorOn(!cursorOn); 127 | } 128 | 129 | setupTextViews(PROMPT + getCurrentLine() + (cursorOn ? "_" : "")); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/LoadingScene.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.entity.Enemy; 5 | 6 | import java.awt.Graphics2D; 7 | import java.awt.image.BufferedImage; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | 12 | public class LoadingScene extends BaseConsoleScene { 13 | 14 | private final HashMap textureCache = new HashMap<>(); 15 | private List itemsToLoad; 16 | private List loadedAssets = new ArrayList<>(); // Keep a reference until GameScene is loaded 17 | private int itemsLoaded = 0; 18 | private boolean loadNextItem = false; 19 | 20 | @Override 21 | public void onLoad() { 22 | super.onLoad(); 23 | onResize(); 24 | 25 | final App app = App.getApp(); 26 | 27 | String version = getClass().getPackage().getImplementationVersion(); 28 | if (version == null) { 29 | version = ""; 30 | } 31 | 32 | App.log("Scared " + version); 33 | App.log("Java " + System.getProperty("java.version")); 34 | 35 | itemsToLoad = new ArrayList<>(); 36 | itemsToLoad.add(new Runnable() { 37 | @Override 38 | public void run() { 39 | 40 | } 41 | }); 42 | 43 | // Audio 44 | itemsToLoad.add(new Runnable() { 45 | @Override 46 | public void run() { 47 | app.getAudio("/sound/bigfan.wav"); 48 | app.getAudio("/sound/door_unlock.wav"); 49 | app.getAudio("/sound/doorclose.wav"); 50 | app.getAudio("/sound/doorwoosh.wav"); 51 | app.getAudio("/sound/endlevel.wav"); 52 | app.getAudio("/sound/enemy_dead.wav"); 53 | app.getAudio("/sound/getammo.wav"); 54 | app.getAudio("/sound/laser0.wav"); 55 | app.getAudio("/sound/laser1.wav"); 56 | app.getAudio("/sound/no_ammo.wav"); 57 | app.getAudio("/sound/nuclear_health.wav"); 58 | app.getAudio("/sound/player_dead.wav"); 59 | app.getAudio("/sound/player_hurt.wav"); 60 | app.getAudio("/sound/startlevel.wav"); 61 | app.getAudio("/sound/unlock.wav"); 62 | app.getAudio("/sound/wallmove.wav"); 63 | } 64 | }); 65 | 66 | // HUD 67 | itemsToLoad.add(new Runnable() { 68 | @Override 69 | public void run() { 70 | loadedAssets.add(app.getImage("/ui/message_font.png")); 71 | loadedAssets.add(app.getImage("/ui/score_font.png")); 72 | loadedAssets.add(app.getImage("/hud/crosshair.png")); 73 | loadedAssets.add(app.getImage("/hud/gun01.png")); 74 | loadedAssets.add(app.getImage("/hud/gun02.png")); 75 | } 76 | }); 77 | 78 | // Game Sprites 79 | // NOTE: Java has trouble with indexed PNG images with a palette of less than 16 colors. 80 | // PNG optimizers create these. Images created from Photoshop or other major tools are fine. 81 | itemsToLoad.add(new Runnable() { 82 | @Override 83 | public void run() { 84 | cacheTexture("/sprites/ammo.png"); 85 | cacheTexture("/sprites/blast1.png"); 86 | cacheTexture("/sprites/blast2.png"); 87 | cacheTexture("/sprites/blast3.png"); 88 | cacheTexture("/sprites/key01.png"); 89 | cacheTexture("/sprites/key02.png"); 90 | cacheTexture("/sprites/key03.png"); 91 | cacheTexture("/sprites/medkit.png"); 92 | cacheTexture("/sprites/nuclear.png"); 93 | for (int i = 0; i < Enemy.NUM_IMAGES; i++) { 94 | cacheTexture("/enemy/" + i + ".png"); 95 | } 96 | } 97 | }); 98 | 99 | // Wall textures 100 | itemsToLoad.add(new Runnable() { 101 | @Override 102 | public void run() { 103 | // All textures must be a size that is a power-of-two. 128x128, 64x64, etc. 104 | String[] textures = { 105 | "door00.png", 106 | "door01.png", 107 | "door02.png", 108 | "door03.png", 109 | "exit00.png", 110 | "exit01.png", 111 | "generator00.png", 112 | "generator01.png", 113 | "wall00.png", 114 | "wall01.png", 115 | "wall02.png", 116 | "wall03.png", 117 | "wall04.png", 118 | "wall05.png", 119 | "wall06.png", 120 | "wall07.png", 121 | "wall08.png", 122 | "wall09.png", 123 | "wall10.png", 124 | "wall11.png", 125 | "wall12.png", 126 | "wall13.png", 127 | "wall14.png", 128 | "wall15.png", 129 | "window00.png", 130 | }; 131 | 132 | // Create mip-maps 133 | final int mipMapCount = 3; 134 | for (String textureName : textures) { 135 | String fullname = "/textures/" + textureName; 136 | SoftTexture texture = cacheTexture(fullname, textureName); 137 | 138 | SoftTexture.DownscaleType downscaleType = SoftTexture.DownscaleType.WEIGHTED_EVEN; 139 | // Hack: Sharpen on odd pixels on these two textures to make their highlights look better 140 | if ("wall01.png".equals(textureName) || "wall06.png".equals(textureName)) { 141 | downscaleType = SoftTexture.DownscaleType.WEIGHTED_ODD; 142 | } 143 | 144 | for (int i = 0; i < mipMapCount; i++) { 145 | texture.createHalfSizeTexture(downscaleType); 146 | texture = texture.getHalfSizeTexture(); 147 | if (texture == null) { 148 | break; 149 | } 150 | downscaleType = SoftTexture.DownscaleType.AVERAGE; 151 | } 152 | } 153 | } 154 | }); 155 | } 156 | 157 | private SoftTexture cacheTexture(String name) { 158 | return cacheTexture(name, name); 159 | } 160 | 161 | private SoftTexture cacheTexture(String fileName, String cacheName) { 162 | App app = App.getApp(); 163 | BufferedImage image = app.getImage(fileName); 164 | loadedAssets.add(image); 165 | SoftTexture texture = new SoftTexture(image); 166 | textureCache.put(cacheName, texture); 167 | return texture; 168 | } 169 | 170 | @Override 171 | public void onTick() { 172 | if (loadNextItem) { 173 | loadNextItem = false; 174 | if (itemsToLoad.isEmpty()) { 175 | App.getApp().setScene(new GameScene(textureCache)); 176 | } else { 177 | Runnable runnable = itemsToLoad.remove(0); 178 | runnable.run(); 179 | itemsLoaded++; 180 | } 181 | } 182 | String s = "Loading"; 183 | for (int i = 0; i < itemsLoaded; i++) { 184 | s += '.'; 185 | } 186 | setupTextViews(s); 187 | } 188 | 189 | public void onDraw(Graphics2D g) { 190 | loadNextItem = true; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/Main.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.app.audio.AudioEngine; 5 | import com.brackeen.app.view.Scene; 6 | 7 | import javax.swing.SwingUtilities; 8 | 9 | public class Main extends App { 10 | 11 | private static final long serialVersionUID = 1L; 12 | 13 | public static void main(String[] args) { 14 | SwingUtilities.invokeLater(new Runnable() { 15 | public void run() { 16 | Main main = new Main(); 17 | main.initFrame(640, 480); 18 | } 19 | }); 20 | } 21 | 22 | public Main() { 23 | AudioEngine.setMasterVolume(Settings.getFloat(Settings.VOLUME, 1.0f)); 24 | 25 | setAppName("Scared"); 26 | setAppIcon("/textures/exit01.png"); 27 | setAudioSampleRate(8000); 28 | setAutoPixelScale(Settings.getBoolean(Settings.AUTO_PIXEL_SCALE, true)); 29 | setAutoPixelScaleBaseSize(320, 240); 30 | } 31 | 32 | @Override 33 | public Scene createFirstScene() { 34 | return new LoadingScene(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/Map.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.action.Action; 5 | import com.brackeen.scared.action.DoorAction; 6 | import com.brackeen.scared.action.GeneratorAction; 7 | import com.brackeen.scared.action.MovableWallAction; 8 | import com.brackeen.scared.entity.Ammo; 9 | import com.brackeen.scared.entity.Enemy; 10 | import com.brackeen.scared.entity.Entity; 11 | import com.brackeen.scared.entity.Key; 12 | import com.brackeen.scared.entity.MedKit; 13 | import com.brackeen.scared.entity.Player; 14 | 15 | import java.awt.geom.Point2D; 16 | import java.io.BufferedReader; 17 | import java.io.IOException; 18 | import java.io.InputStream; 19 | import java.io.InputStreamReader; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.HashMap; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | 26 | public class Map { 27 | 28 | private int width; 29 | private int height; 30 | 31 | private final MessageQueue messageQueue; 32 | private final Player player; 33 | private final List entities = new ArrayList<>(); 34 | private final List actions = new ArrayList<>(); 35 | private final SoftTexture exitButtonOnTexture; 36 | private final SoftTexture generatorOnTexture; 37 | private SoftTexture defaultFloorTexture; 38 | private Tile[][] tiles; 39 | private boolean electricityOn = true; 40 | private boolean exitFound = false; 41 | private Tile lastCollidedWall; 42 | 43 | private int numSecrets = 0; 44 | private int numEnemies = 0; 45 | 46 | public Map(HashMap textureCache, MessageQueue messageQueue, String mapName, Player oldPlayer, Stats stats) throws IOException { 47 | this.messageQueue = messageQueue; 48 | 49 | SoftTexture[] enemyTextures = new SoftTexture[Enemy.NUM_IMAGES]; 50 | for (int i = 0; i < Enemy.NUM_IMAGES; i++) { 51 | enemyTextures[i] = textureCache.get("/enemy/" + i + ".png"); 52 | } 53 | 54 | defaultFloorTexture = textureCache.get("wall00.png"); 55 | generatorOnTexture = textureCache.get("generator01.png"); 56 | exitButtonOnTexture = textureCache.get("exit01.png"); 57 | 58 | player = new Player(this); 59 | if (oldPlayer != null) { 60 | player.setHealth(Math.max(oldPlayer.getHealth(), player.getHealth())); 61 | player.setAmmo(Math.max(oldPlayer.getAmmo(), player.getAmmo())); 62 | player.setGodMode(oldPlayer.isGodMode()); 63 | player.setFreezeEnemies(oldPlayer.isFreezeEnemies()); 64 | } 65 | addEntity(player); 66 | 67 | InputStream stream = App.getResourceAsStream(mapName); 68 | if (stream == null) { 69 | throw new IOException("Not found: " + mapName); 70 | } 71 | BufferedReader r = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 72 | String line; 73 | try { 74 | // Width 75 | line = r.readLine(); 76 | if (!line.startsWith("w=")) { 77 | throw new IOException("Illegal width line: " + line); 78 | } 79 | width = Integer.parseInt(line.substring(2)); 80 | 81 | // Height 82 | line = r.readLine(); 83 | if (!line.startsWith("h=")) { 84 | throw new IOException("Illegal height line: " + line); 85 | } 86 | height = Integer.parseInt(line.substring(2)); 87 | 88 | // Direction 89 | line = r.readLine(); 90 | if (!line.startsWith("dir=")) { 91 | throw new IOException("Illegal dir line: " + line); 92 | } 93 | player.setDirection(Integer.parseInt(line.substring(4))); 94 | 95 | tiles = new Tile[width][height]; 96 | 97 | // Read tile types 98 | for (int y = 0; y < height; y++) { 99 | line = r.readLine(); 100 | if (line.length() != width) { 101 | throw new IOException("Wrong width: " + line); 102 | } 103 | for (int x = 0; x < width; x++) { 104 | Tile tile = new Tile(); 105 | 106 | tiles[x][y] = tile; 107 | 108 | switch (line.charAt(x)) { 109 | case ' ': 110 | tile.type = Tile.TYPE_NOTHING; 111 | break; 112 | case '#': 113 | tile.type = Tile.TYPE_WALL; 114 | break; 115 | case 'A': 116 | tile.type = Tile.TYPE_DOOR; 117 | break; 118 | case 'B': 119 | tile.type = Tile.TYPE_DOOR; 120 | tile.subtype = 1; 121 | break; 122 | case 'C': 123 | tile.type = Tile.TYPE_DOOR; 124 | tile.subtype = 2; 125 | break; 126 | case 'D': 127 | tile.type = Tile.TYPE_DOOR; 128 | tile.subtype = 3; 129 | break; 130 | case '-': 131 | tile.type = Tile.TYPE_WINDOW; 132 | tile.subtype = 1; 133 | break; 134 | case '|': 135 | tile.type = Tile.TYPE_WINDOW; 136 | tile.subtype = 2; 137 | break; 138 | case '+': 139 | tile.type = Tile.TYPE_WINDOW; 140 | tile.subtype = 3; 141 | break; 142 | case '*': 143 | tile.type = Tile.TYPE_GENERATOR; 144 | setElectricityOn(false); 145 | break; 146 | case '@': 147 | tile.type = Tile.TYPE_MOVABLE_WALL; 148 | numSecrets++; 149 | break; 150 | case 'X': 151 | tile.type = Tile.TYPE_EXIT; 152 | break; 153 | case 'S': 154 | tile.type = Tile.TYPE_NOTHING; 155 | player.setLocation(x + 0.5f, y + 0.5f); 156 | break; 157 | case '^': 158 | tile.type = Tile.TYPE_NOTHING; 159 | addEntity(new Enemy(this, stats, enemyTextures, x + 0.5f, y + 0.5f, 1)); 160 | numEnemies++; 161 | break; 162 | case 'b': 163 | tile.type = Tile.TYPE_NOTHING; 164 | addEntity(new Key(this, textureCache.get("/sprites/key01.png"), x + 0.5f, y + 0.5f, 1)); 165 | break; 166 | case 'c': 167 | tile.type = Tile.TYPE_NOTHING; 168 | addEntity(new Key(this, textureCache.get("/sprites/key02.png"), x + 0.5f, y + 0.5f, 2)); 169 | break; 170 | case 'd': 171 | tile.type = Tile.TYPE_NOTHING; 172 | addEntity(new Key(this, textureCache.get("/sprites/key03.png"), x + 0.5f, y + 0.5f, 3)); 173 | break; 174 | case 'h': 175 | tile.type = Tile.TYPE_NOTHING; 176 | addEntity(new MedKit(this, textureCache.get("/sprites/medkit.png"), x + 0.5f, y + 0.5f, false)); 177 | break; 178 | case 'H': 179 | tile.type = Tile.TYPE_NOTHING; 180 | addEntity(new MedKit(this, textureCache.get("/sprites/nuclear.png"), x + 0.5f, y + 0.5f, true)); 181 | break; 182 | case 'm': 183 | tile.type = Tile.TYPE_NOTHING; 184 | addEntity(new Ammo(this, textureCache.get("/sprites/ammo.png"), x + 0.5f, y + 0.5f)); 185 | break; 186 | default: 187 | tile.type = Tile.TYPE_NOTHING; 188 | App.logError("Map " + mapName + " contains invalid char: " + line.charAt(x)); 189 | break; 190 | } 191 | } 192 | } 193 | 194 | r.readLine(); 195 | 196 | // Read textures 197 | for (int y = 0; y < height; y++) { 198 | line = r.readLine(); 199 | if (line.length() != width) { 200 | throw new IOException("Wrong width: " + line); 201 | } 202 | for (int x = 0; x < width; x++) { 203 | Tile tile = tiles[x][y]; 204 | 205 | if (tile.type == Tile.TYPE_GENERATOR) { 206 | tile.setTexture(textureCache.get("generator00.png")); 207 | } else if (tile.type == Tile.TYPE_EXIT) { 208 | tile.setTexture(textureCache.get("exit00.png")); 209 | } else { 210 | int textureIndex = Integer.parseInt(line.substring(x, x + 1), 16); 211 | if (textureIndex < 10) { 212 | tile.setTexture(textureCache.get("wall0" + textureIndex + ".png")); 213 | } else { 214 | tile.setTexture(textureCache.get("wall" + textureIndex + ".png")); 215 | } 216 | } 217 | } 218 | } 219 | } catch (NumberFormatException ex) { 220 | throw new IOException(ex); 221 | } 222 | } 223 | 224 | public void setMessage(String message) { 225 | messageQueue.add(message); 226 | } 227 | 228 | public SoftTexture getDefaultFloorTexture() { 229 | return defaultFloorTexture; 230 | } 231 | 232 | public void setDefaultFloorTexture(SoftTexture defaultFloorTexture) { 233 | this.defaultFloorTexture = defaultFloorTexture; 234 | } 235 | 236 | public boolean isExitFound() { 237 | return exitFound; 238 | } 239 | 240 | public void tick() { 241 | // Handle regular actions 242 | Iterator i = actions.iterator(); 243 | while (i.hasNext()) { 244 | Action action = i.next(); 245 | action.tick(); 246 | if (action.isFinished()) { 247 | action.unload(); 248 | i.remove(); 249 | } 250 | } 251 | 252 | // Move entities 253 | Iterator i2 = entities.iterator(); 254 | while (i2.hasNext()) { 255 | Entity entity = i2.next(); 256 | tickEntity(entity); 257 | if (entity.isDeleted()) { 258 | i2.remove(); 259 | } 260 | } 261 | } 262 | 263 | private boolean tickEntity(Entity entity) { 264 | Tile oldTile = entity.getTile(); 265 | entity.tick(); 266 | if (entity.isDeleted()) { 267 | if (oldTile != null) { 268 | oldTile.removeEntity(entity); 269 | } 270 | return true; 271 | } else { 272 | Tile newTile = getTileAt(entity); 273 | if (oldTile != newTile) { 274 | if (oldTile != null) { 275 | oldTile.removeEntity(entity); 276 | } 277 | if (newTile != null) { 278 | newTile.addEntity(entity); 279 | } 280 | } 281 | return false; 282 | } 283 | } 284 | 285 | public void unload() { 286 | for (Action action : actions) { 287 | action.unload(); 288 | } 289 | } 290 | 291 | public int getNumActions() { 292 | return actions.size(); 293 | } 294 | 295 | public int getNumEntities() { 296 | return entities.size(); 297 | } 298 | 299 | public void addEntity(Entity entity) { 300 | entities.add(entity); 301 | 302 | Tile tile = getTileAt(entity); 303 | 304 | if (tile != null) { 305 | tile.addEntity(entity); 306 | } 307 | } 308 | 309 | public Player getPlayer() { 310 | return player; 311 | } 312 | 313 | public boolean isElectricityOn() { 314 | return electricityOn; 315 | } 316 | 317 | public void setElectricityOn(boolean electricityOn) { 318 | this.electricityOn = electricityOn; 319 | } 320 | 321 | public int getWidth() { 322 | return width; 323 | } 324 | 325 | public int getHeight() { 326 | return height; 327 | } 328 | 329 | public int getNumEnemies() { 330 | return numEnemies; 331 | } 332 | 333 | public int getNumSecrets() { 334 | return numSecrets; 335 | } 336 | 337 | public Tile getTileAt(Entity entity) { 338 | return getTileAt((int) entity.getX(), (int) entity.getY()); 339 | } 340 | 341 | public Tile getTileAt(int x, int y) { 342 | if (x < 0 || y < 0 || x >= width || y >= height) { 343 | return null; 344 | } 345 | 346 | return tiles[x][y]; 347 | } 348 | 349 | public boolean isSolidAt(int tileX, int tileY) { 350 | Tile tile = getTileAt(tileX, tileY); 351 | return (tile == null || tile.isSolid()); 352 | } 353 | 354 | public void notifyPlayerEnteredTile(int tileX, int tileY) { 355 | Tile tile = getTileAt(tileX, tileY); 356 | if (tile != null && isElectricityOn()) { 357 | // check if surrounding tiles are a door 358 | if (isUnlockedDoor(tileX, tileY - 1)) { 359 | activateDoor(tileX, tileY - 1); 360 | } 361 | if (isUnlockedDoor(tileX, tileY + 1)) { 362 | activateDoor(tileX, tileY + 1); 363 | } 364 | if (isUnlockedDoor(tileX - 1, tileY)) { 365 | activateDoor(tileX - 1, tileY); 366 | } 367 | if (isUnlockedDoor(tileX + 1, tileY)) { 368 | activateDoor(tileX + 1, tileY); 369 | } 370 | } 371 | } 372 | 373 | private boolean isUnlockedDoor(int tileX, int tileY) { 374 | Tile tile = getTileAt(tileX, tileY); 375 | if (tile != null) { 376 | if (tile.type == Tile.TYPE_DOOR && player.hasKey(tile.getDoorType())) { 377 | return true; 378 | } 379 | } 380 | return false; 381 | } 382 | 383 | private void activateDoor(int tileX, int tileY) { 384 | // Check to see if there already is a door action 385 | for (Action action : actions) { 386 | if (action instanceof DoorAction) { 387 | DoorAction doorAction = (DoorAction) action; 388 | 389 | if (doorAction.getTileX() == tileX && 390 | doorAction.getTileY() == tileY) { 391 | return; 392 | } 393 | } 394 | } 395 | actions.add(new DoorAction(this, tileX, tileY)); 396 | } 397 | 398 | public void notifyPlayerTouchedNoWall() { 399 | lastCollidedWall = null; 400 | } 401 | 402 | public void notifyPlayerTouchedWall(Tile tile, int tileX, int tileY) { 403 | if (tile.type == Tile.TYPE_MOVABLE_WALL) { 404 | if (tile.state == MovableWallAction.STATE_DONE) { 405 | int dx = tileX - (int) player.getX(); 406 | int dy = tileY - (int) player.getY(); 407 | 408 | if ((dx == 0 && Math.abs(dy) == 1) || (dy == 0 && Math.abs(dx) == 1)) { 409 | actions.add(new MovableWallAction(this, tileX, tileY)); 410 | player.setSecrets(player.getSecrets() + 1); 411 | } 412 | } 413 | } else if (tile.type == Tile.TYPE_EXIT) { 414 | if (tile.state == 0) { 415 | tile.state = 1; 416 | tile.setTexture(exitButtonOnTexture); 417 | App.getApp().getAudio("/sound/endlevel.wav").play(); 418 | exitFound = true; 419 | } 420 | } else if (tile.type == Tile.TYPE_GENERATOR) { 421 | if (tile.state == 0) { 422 | tile.state = 1; 423 | tile.setTexture(generatorOnTexture); 424 | actions.add(new GeneratorAction(this, tileX, tileY)); 425 | setElectricityOn(true); 426 | setMessage("The power is now on"); 427 | } 428 | } else if (tile.type == Tile.TYPE_DOOR) { 429 | if (tile != lastCollidedWall) { 430 | lastCollidedWall = tile; 431 | if (!electricityOn) { 432 | setMessage("The power is off"); 433 | App.getApp().getAudio("/sound/no_ammo.wav").play(); 434 | } else if (!player.hasKey(tile.getDoorType())) { 435 | setMessage("The door is locked"); 436 | App.getApp().getAudio("/sound/no_ammo.wav").play(); 437 | } 438 | } 439 | } 440 | } 441 | 442 | public List getCollisions(Class entityClass, 443 | float x1, float y1, float x2, float y2) { 444 | List hitEntities = new ArrayList<>(); 445 | 446 | float dx = x2 - x1; 447 | float dy = y2 - y1; 448 | float segmentLengthSq = dx * dx + dy * dy; 449 | 450 | List entitiesToSearch; 451 | if (entityClass == Player.class) { 452 | entitiesToSearch = Collections.singletonList(player); 453 | } else { 454 | entitiesToSearch = entities; 455 | } 456 | 457 | for (Entity entity : entitiesToSearch) { 458 | float radius = entity.getRadius(); 459 | if (radius > 0 && entity.getClass().isAssignableFrom(entityClass)) { 460 | 461 | // Point-to-line distance 462 | float pointX = entity.getX(); 463 | float pointY = entity.getY(); 464 | dx = pointX - x1; 465 | dy = pointY - y1; 466 | 467 | float u = (dx * (x2 - x1) + dy * (y2 - y1)) / segmentLengthSq; 468 | 469 | if (u < 0 || u > 1) { 470 | // Not within the segment 471 | continue; 472 | } 473 | 474 | float intersectionX = x1 + u * (x2 - x1); 475 | float intersectionY = y1 + u * (y2 - y1); 476 | 477 | dx = pointX - intersectionX; 478 | dy = pointY - intersectionY; 479 | float distToRaySq = dx * dx + dy * dy; 480 | float radiusSq = radius * radius; 481 | if (distToRaySq <= radiusSq) { 482 | hitEntities.add(entity); 483 | } 484 | } 485 | } 486 | return hitEntities; 487 | } 488 | 489 | public Point2D.Float getWallCollision(float x, float y, float angleInDegrees) { 490 | int tileX = (int) x; 491 | int tileY = (int) y; 492 | 493 | if (isSolidAt(tileX, tileY)) { 494 | return null; 495 | } 496 | 497 | float dx = (float) Math.cos(Math.toRadians(angleInDegrees)); 498 | float dy = (float) Math.sin(Math.toRadians(angleInDegrees)); 499 | 500 | Point2D.Float p1 = getCollisionPart2(x, y, dx, dy, false); 501 | Point2D.Float p2 = getCollisionPart2(y, x, dy, dx, true); 502 | 503 | if (p1 == null) { 504 | return p2; 505 | } else if (p2 == null) { 506 | return p1; 507 | } else { 508 | // Nearest collision 509 | float dx1 = p1.x - x; 510 | float dy1 = p1.y - y; 511 | float dx2 = p2.x - x; 512 | float dy2 = p2.y - y; 513 | 514 | float d1Sq = dx1 * dx1 + dy1 * dy1; 515 | float d2Sq = dx2 * dx2 + dy2 * dy2; 516 | 517 | if (d1Sq < d2Sq) { 518 | return p1; 519 | } else { 520 | return p2; 521 | } 522 | } 523 | } 524 | 525 | private Point2D.Float getCollisionPart2(float x, float y, float dx, float dy, boolean inversed) { 526 | if (dx == 0) { 527 | return null; 528 | } 529 | 530 | float fx; 531 | float fdx; 532 | 533 | if (dx < 0) { 534 | fx = (float) Math.floor(x) - 0.00001f; 535 | fdx = -1; 536 | } else { 537 | fx = (float) Math.ceil(x); 538 | fdx = 1; 539 | } 540 | 541 | float fdy = dy / Math.abs(dx); 542 | float fy = y + Math.abs(fx - x) * fdy; 543 | 544 | if (inversed) { 545 | return getCollisionPart3(fy, fx, fdy, fdx); 546 | } else { 547 | return getCollisionPart3(fx, fy, fdx, fdy); 548 | } 549 | } 550 | 551 | private Point2D.Float getCollisionPart3(float x, float y, float dx, float dy) { 552 | while (true) { 553 | int tileX = (int) x; 554 | int tileY = (int) y; 555 | if (isSolidAt(tileX, tileY)) { 556 | return new Point2D.Float(x, y); 557 | } 558 | 559 | x += dx; 560 | y += dy; 561 | } 562 | } 563 | } 564 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/MessageQueue.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | public class MessageQueue { 8 | 9 | private static class Message { 10 | final String text; 11 | int ticksRemaining; 12 | 13 | public Message(String text) { 14 | this.text = text; 15 | ticksRemaining = 100; 16 | } 17 | } 18 | 19 | private final List messages; 20 | private final int maxSize; 21 | 22 | public MessageQueue(int size) { 23 | maxSize = size; 24 | messages = new ArrayList<>(); 25 | } 26 | 27 | public void tick() { 28 | Iterator i = messages.iterator(); 29 | while (i.hasNext()) { 30 | Message m = i.next(); 31 | m.ticksRemaining--; 32 | if (m.ticksRemaining <= 0) { 33 | i.remove(); 34 | } 35 | } 36 | } 37 | 38 | public int getMaxSize() { 39 | return maxSize; 40 | } 41 | 42 | public int size() { 43 | return messages.size(); 44 | } 45 | 46 | public String get(int i) { 47 | if (i < size()) { 48 | return messages.get(i).text; 49 | } else { 50 | return null; 51 | } 52 | } 53 | 54 | public void add(String text) { 55 | messages.add(new Message(text)); 56 | while (messages.size() > maxSize) { 57 | messages.remove(0); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/Settings.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import java.util.prefs.Preferences; 4 | 5 | public class Settings { 6 | 7 | public static final String AUTO_PIXEL_SCALE = "autoPixelScale"; 8 | public static final String DEPTH_SHADING = "depthShading"; 9 | public static final String VOLUME = "volume"; 10 | 11 | public static Preferences getPrefs() { 12 | Preferences prefs; 13 | try { 14 | prefs = Preferences.userNodeForPackage(Settings.class); 15 | } catch (SecurityException ex) { 16 | prefs = null; 17 | } 18 | return prefs; 19 | } 20 | 21 | public static void putFloat(String name, float value) { 22 | Preferences prefs = getPrefs(); 23 | if (prefs != null) { 24 | prefs.putFloat(name, value); 25 | } 26 | } 27 | 28 | public static float getFloat(String name, float defaultValue) { 29 | Preferences prefs = getPrefs(); 30 | if (prefs != null) { 31 | return prefs.getFloat(name, defaultValue); 32 | } else { 33 | return defaultValue; 34 | } 35 | } 36 | 37 | public static void putBoolean(String name, boolean value) { 38 | Preferences prefs = getPrefs(); 39 | if (prefs != null) { 40 | prefs.putBoolean(name, value); 41 | } 42 | } 43 | 44 | public static boolean getBoolean(String name, boolean defaultValue) { 45 | Preferences prefs = getPrefs(); 46 | if (prefs != null) { 47 | return prefs.getBoolean(name, defaultValue); 48 | } else { 49 | return defaultValue; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/SoftTexture.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | 5 | import java.awt.Point; 6 | import java.awt.image.BufferedImage; 7 | import java.awt.image.DataBuffer; 8 | import java.awt.image.DataBufferInt; 9 | import java.awt.image.DirectColorModel; 10 | import java.awt.image.Raster; 11 | import java.awt.image.SampleModel; 12 | import java.awt.image.SinglePixelPackedSampleModel; 13 | import java.awt.image.WritableRaster; 14 | 15 | /** 16 | * A software texture for software rendering. Stored in normal RAM, instead of video ram. 17 | */ 18 | public class SoftTexture { 19 | 20 | public enum DownscaleType { 21 | AVERAGE, 22 | WEIGHTED_EVEN, 23 | WEIGHTED_ODD, 24 | } 25 | 26 | private static int[] getImageData(BufferedImage image) { 27 | int w = image.getWidth(); 28 | int h = image.getHeight(); 29 | int[] data = null; 30 | if (image.getType() == BufferedImage.TYPE_INT_ARGB) { 31 | // Common case - use the existing array 32 | data = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 33 | if (data.length != w * h) { 34 | data = null; 35 | } 36 | } 37 | if (data == null) { 38 | // Copies the data to a new array 39 | data = image.getRGB(0, 0, w, h, null, 0, w); 40 | } 41 | return data; 42 | } 43 | 44 | private static boolean isPowerOfTwo(int n) { 45 | return (n & (n - 1)) == 0; 46 | } 47 | 48 | private static int log2(int n) { 49 | int count = 0; 50 | while (true) { 51 | n >>= 1; 52 | if (n == 0) { 53 | return count; 54 | } 55 | count++; 56 | } 57 | } 58 | 59 | public static BufferedImage createHalfSizeImage(BufferedImage image) { 60 | SoftTexture texture = new SoftTexture(image); 61 | boolean success = texture.createHalfSizeTexture(DownscaleType.AVERAGE); 62 | if (success) { 63 | return texture.getHalfSizeTexture().getBufferedImageView(); 64 | } else { 65 | return null; 66 | } 67 | } 68 | 69 | private final int width; 70 | private final int height; 71 | private final int sizeBits; 72 | private final int[] data; 73 | private SoftTexture halfSizeTexture; // For mip-mapping 74 | 75 | public SoftTexture(int width, int height) { 76 | this.width = width; 77 | this.height = height; 78 | this.data = new int[width * height]; 79 | if (isPowerOfTwo(width) && width == height) { 80 | sizeBits = log2(width); 81 | } else { 82 | sizeBits = -1; 83 | } 84 | } 85 | 86 | public SoftTexture(String imageName) { 87 | this(App.getApp().getImage(imageName)); 88 | } 89 | 90 | public SoftTexture(BufferedImage image) { 91 | this.width = image.getWidth(); 92 | this.height = image.getHeight(); 93 | this.data = getImageData(image); 94 | if (isPowerOfTwo(width) && width == height) { 95 | sizeBits = log2(width); 96 | } else { 97 | sizeBits = -1; 98 | } 99 | } 100 | 101 | public boolean hasHalfSizeTexture() { 102 | return halfSizeTexture != null; 103 | } 104 | 105 | public SoftTexture getHalfSizeTexture() { 106 | return halfSizeTexture; 107 | } 108 | 109 | public void setHalfSizeTexture(SoftTexture halfSizeTexture) { 110 | this.halfSizeTexture = halfSizeTexture; 111 | } 112 | 113 | public int getWidth() { 114 | return width; 115 | } 116 | 117 | public int getHeight() { 118 | return height; 119 | } 120 | 121 | public boolean isPowerOfTwo() { 122 | return sizeBits >= 0; 123 | } 124 | 125 | public int getSizeBits() { 126 | return sizeBits; 127 | } 128 | 129 | public int[] getData() { 130 | return data; 131 | } 132 | 133 | public BufferedImage getBufferedImageView() { 134 | DirectColorModel colorModel = new DirectColorModel(24, 0xff0000, 0x00ff00, 0x0000ff); 135 | SampleModel sampleModel = new SinglePixelPackedSampleModel( 136 | DataBuffer.TYPE_INT, width, height, new int[]{0xff0000, 0x00ff00, 0x0000ff}); 137 | DataBuffer dataBuffer = new DataBufferInt(data, width * height); 138 | WritableRaster raster = Raster.createWritableRaster( 139 | sampleModel, dataBuffer, new Point(0, 0)); 140 | return new BufferedImage(colorModel, raster, true, null); 141 | } 142 | 143 | public boolean createHalfSizeTexture(DownscaleType downscaleType) { 144 | if ((width & 1) != 0 || (height & 1) != 0) { 145 | return false; 146 | } 147 | halfSizeTexture = new SoftTexture(width / 2, height / 2); 148 | int srcOffset = 0; 149 | int dstOffset = 0; 150 | for (int y = 0; y < height; y += 2) { 151 | switch (downscaleType) { 152 | case AVERAGE: 153 | for (int x = 0; x < width; x += 2) { 154 | int c1 = data[srcOffset]; 155 | int c2 = data[srcOffset + 1]; 156 | int c3 = data[srcOffset + width]; 157 | int c4 = data[srcOffset + width + 1]; 158 | 159 | int a = ((c1 >>> 24) + (c2 >>> 24) + (c3 >>> 24) + (c4 >>> 24)) >> 2; 160 | int r = (((c1 >> 16) & 0xff) + ((c2 >> 16) & 0xff) + ((c3 >> 16) & 0xff) + ((c4 >> 16) & 0xff)) >> 2; 161 | int g = (((c1 >> 8) & 0xff) + ((c2 >> 8) & 0xff) + ((c3 >> 8) & 0xff) + ((c4 >> 8) & 0xff)) >> 2; 162 | int b = ((c1 & 0xff) + (c2 & 0xff) + (c3 & 0xff) + (c4 & 0xff)) >> 2; 163 | 164 | halfSizeTexture.data[dstOffset++] = (a << 24) | (r << 16) | (g << 8) | b; 165 | srcOffset += 2; 166 | } 167 | break; 168 | case WEIGHTED_EVEN: 169 | for (int x = 0; x < width; x += 2) { 170 | int c1 = data[srcOffset]; 171 | int c2 = data[srcOffset + 1]; 172 | int c3 = data[srcOffset + width]; 173 | int c4 = data[srcOffset + width + 1]; 174 | 175 | int a = ((c1 >>> 24) * 5 + (c2 >>> 24) + (c3 >>> 24) + (c4 >>> 24)) >> 3; 176 | int r = (((c1 >> 16) & 0xff) * 5 + ((c2 >> 16) & 0xff) + ((c3 >> 16) & 0xff) + ((c4 >> 16) & 0xff)) >> 3; 177 | int g = (((c1 >> 8) & 0xff) * 5 + ((c2 >> 8) & 0xff) + ((c3 >> 8) & 0xff) + ((c4 >> 8) & 0xff)) >> 3; 178 | int b = ((c1 & 0xff) * 5 + (c2 & 0xff) + (c3 & 0xff) + (c4 & 0xff)) >> 3; 179 | 180 | halfSizeTexture.data[dstOffset++] = (a << 24) | (r << 16) | (g << 8) | b; 181 | srcOffset += 2; 182 | } 183 | break; 184 | case WEIGHTED_ODD: 185 | for (int x = 0; x < width; x += 2) { 186 | int c1 = data[srcOffset]; 187 | int c2 = data[srcOffset + 1]; 188 | int c3 = data[srcOffset + width]; 189 | int c4 = data[srcOffset + width + 1]; 190 | 191 | int a = ((c1 >>> 24) + (c2 >>> 24) + (c3 >>> 24) + (c4 >>> 24) * 5) >> 3; 192 | int r = (((c1 >> 16) & 0xff) + ((c2 >> 16) & 0xff) + ((c3 >> 16) & 0xff) + ((c4 >> 16) & 0xff) * 5) >> 3; 193 | int g = (((c1 >> 8) & 0xff) + ((c2 >> 8) & 0xff) + ((c3 >> 8) & 0xff) + ((c4 >> 8) & 0xff) * 5) >> 3; 194 | int b = ((c1 & 0xff) + (c2 & 0xff) + (c3 & 0xff) + (c4 & 0xff) * 5) >> 3; 195 | 196 | halfSizeTexture.data[dstOffset++] = (a << 24) | (r << 16) | (g << 8) | b; 197 | srcOffset += 2; 198 | } 199 | break; 200 | } 201 | 202 | srcOffset += width; 203 | } 204 | return true; 205 | } 206 | 207 | /** 208 | * Draws the specified texture (source) onto this texture (dest). 209 | */ 210 | public void draw(SoftTexture src, int x, int y, boolean srcOpaque) { 211 | draw(src, x, y, 0, 0, src.width, src.height, srcOpaque); 212 | } 213 | 214 | /** 215 | * Draws the specified texture (source) onto this texture (dest). 216 | */ 217 | public void draw(SoftTexture src, int x, int y, int srcX, int srcY, int srcWidth, int srcHeight, boolean srcOpaque) { 218 | SoftTexture dest = this; 219 | int destX = x; 220 | int destY = y; 221 | 222 | // Clip to dest 223 | if (destX < 0) { 224 | srcX = -destX; 225 | srcWidth += destX; 226 | destX = 0; 227 | } 228 | if (destY < 0) { 229 | srcY = -destY; 230 | srcHeight += destY; 231 | destY = 0; 232 | } 233 | if (destX + srcWidth > dest.width) { 234 | srcWidth = dest.width - destX; 235 | } 236 | if (destY + srcHeight > dest.height) { 237 | srcHeight = dest.height - destY; 238 | } 239 | 240 | if (srcWidth <= 0 || srcHeight <= 0) { 241 | return; 242 | } 243 | 244 | // Draw 245 | int[] srcData = src.data; 246 | int[] destData = dest.data; 247 | int srcOffset = srcX + srcY * src.width; 248 | int destOffset = destX + destY * dest.width; 249 | 250 | for (int i = 0; i < srcHeight; i++) { 251 | if (srcOpaque) { 252 | System.arraycopy(srcData, srcOffset, destData, destOffset, srcWidth); 253 | } else { 254 | for (int j = 0; j < srcWidth; j++) { 255 | int color = srcData[srcOffset + j]; 256 | 257 | if (color != 0) { 258 | destData[destOffset + j] = color; 259 | } 260 | } 261 | } 262 | 263 | srcOffset += src.width; 264 | destOffset += dest.width; 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/SoundPlayer3D.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.app.audio.AudioBuffer; 5 | import com.brackeen.scared.entity.Entity; 6 | 7 | public class SoundPlayer3D { 8 | 9 | // Prevent instantiation 10 | private SoundPlayer3D() { 11 | } 12 | 13 | private static final int MAX_DIST = 24; 14 | 15 | public static void play(String audioName, Entity listener, int sourceTileX, int sourceTileY) { 16 | AudioBuffer audio = App.getApp().getAudio(audioName); 17 | if (audio != null) { 18 | audio.play(getVolume(listener, sourceTileX, sourceTileY), 19 | getPan(listener, sourceTileX, sourceTileY), 20 | false); 21 | } 22 | } 23 | 24 | public static void play(String audioName, Entity listener, Entity source) { 25 | AudioBuffer audio = App.getApp().getAudio(audioName); 26 | if (audio != null) { 27 | audio.play(getVolume(listener, source), 28 | getPan(listener, source), 29 | false); 30 | } 31 | } 32 | 33 | public static float getVolume(Entity listener, int sourceTileX, int sourceTileY) { 34 | return getVolume((sourceTileX + 0.5f) - listener.getX(), 35 | (sourceTileY + 0.5f) - listener.getY()); 36 | } 37 | 38 | public static float getVolume(Entity listener, Entity source) { 39 | return getVolume(source.getX() - listener.getX(), 40 | source.getY() - listener.getY()); 41 | } 42 | 43 | private static float getVolume(float sourceX, float sourceY) { 44 | float dist = (float) (Math.sqrt(sourceX * sourceX + sourceY * sourceY)); 45 | 46 | if (dist < 0 || dist >= MAX_DIST) { 47 | return 0; 48 | } else if (dist <= 1) { 49 | return 1; 50 | } else { 51 | return 1 / dist; 52 | } 53 | } 54 | 55 | public static float getPan(Entity listener, int sourceTileX, int sourceTileY) { 56 | return getPan(listener.getDirection(), 57 | (sourceTileX + 0.5f) - listener.getX(), 58 | (sourceTileY + 0.5f) - listener.getY()); 59 | } 60 | 61 | public static float getPan(Entity listener, Entity source) { 62 | return getPan(listener.getDirection(), 63 | source.getX() - listener.getX(), 64 | source.getY() - listener.getY()); 65 | } 66 | 67 | private static float getPan(float listenerDirection, float sourceX, float sourceY) { 68 | // Vector a = direction of player view 69 | // Vector b = direction of sound source (from player) 70 | // side = the side point b is on of line a (1, 0, or -1) 71 | // pan = side * (1 - abs((a.b)/(|a|*|b|))) 72 | 73 | // NOTE: Scared has a fucked-up coordinate system because I didn't know 74 | // what I was doing when I made this game in 1997-1998, which was based on code 75 | // I wrote in C as a teenager. 76 | // The angle, cos table, and sin table are all correct. Y values need to be inversed. 77 | 78 | float aX = (float) Math.cos(Math.toRadians(listenerDirection)); 79 | float aY = (float) Math.sin(Math.toRadians(listenerDirection)); 80 | float bX = sourceX; 81 | float bY = -sourceY; 82 | float bLength = (float) (Math.sqrt(bX * bX + bY * bY)); 83 | if (bLength <= 0) { 84 | return 0; 85 | } 86 | float dotProduct = aX * bX + aY * bY; 87 | 88 | // Dot product between location b and the perpendicular to vector a 89 | float side = Math.signum(bX * aY - aX * bY); 90 | 91 | float cosAngle = dotProduct / bLength; 92 | 93 | return side * (1 - Math.abs(cosAngle)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/Stats.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class Stats { 6 | public int numDeaths; 7 | public int numShotsFired; 8 | public int numShotsFiredHit; 9 | public int numEnemyShotsFired; 10 | public int numEnemyShotsFiredHit; 11 | public boolean cheated; 12 | public long startTime; 13 | 14 | public Stats() { 15 | startTime = System.nanoTime(); 16 | } 17 | 18 | // End-of-level stats 19 | public int numSecretsFound; 20 | public int numKills; 21 | public int totalSecrets; 22 | public int totalEnemies; 23 | 24 | public String getDescription(Map map, int level) { 25 | final int padWidth = 36; 26 | String playerAccuracy = (numShotsFired == 0) ? "N/A" : String.format("%.1f%%", 27 | numShotsFiredHit * 100.0f / numShotsFired); 28 | String enemyAccuracy = (numEnemyShotsFired == 0) ? "N/A" : String.format("%.1f%%", 29 | numEnemyShotsFiredHit * 100.0f / numEnemyShotsFired); 30 | return (padLine(" Level: " + level + "/" + GameScene.NUM_LEVELS, padWidth) + "\n" + 31 | padLine(" Shots fired: " + numShotsFired, padWidth) + "\n" + 32 | padLine(" Accuracy: " + playerAccuracy, padWidth) + "\n" + 33 | padLine(" Enemies killed: " + (numKills + map.getPlayer().getKills()) + "/" + (totalEnemies + map.getNumEnemies()), padWidth) + "\n" + 34 | padLine("Enemy shots fired: " + numEnemyShotsFired, padWidth) + "\n" + 35 | padLine(" Enemy Accuracy: " + enemyAccuracy, padWidth) + "\n" + 36 | padLine(" Secrets found: " + (numSecretsFound + map.getPlayer().getSecrets()) + "/" + (totalSecrets + map.getNumSecrets()), padWidth) + "\n" + 37 | padLine(" Deaths: " + numDeaths, padWidth) + "\n" + 38 | padLine(" Time: " + getTimeElapsed(startTime), padWidth) + "\n" + 39 | padLine(" Cheated: " + (cheated ? "YES" : "NO"), padWidth)); 40 | } 41 | 42 | private static String getTimeElapsed(long startTimeNanos) { 43 | long nanos = System.nanoTime() - startTimeNanos; 44 | return String.format("%02d:%02d:%02d.%03d", 45 | TimeUnit.NANOSECONDS.toHours(nanos), 46 | TimeUnit.NANOSECONDS.toMinutes(nanos) - 47 | TimeUnit.HOURS.toMinutes(TimeUnit.NANOSECONDS.toHours(nanos)), 48 | TimeUnit.NANOSECONDS.toSeconds(nanos) - 49 | TimeUnit.MINUTES.toSeconds(TimeUnit.NANOSECONDS.toMinutes(nanos)), 50 | TimeUnit.NANOSECONDS.toMillis(nanos) - 51 | TimeUnit.SECONDS.toMillis(TimeUnit.NANOSECONDS.toSeconds(nanos)) 52 | ); 53 | } 54 | 55 | private static String padLine(String line, int minLength) { 56 | // Poor man's center on the colon 57 | if (line != null && line.length() < minLength) { 58 | StringBuilder sb = new StringBuilder(line); 59 | while (sb.length() < minLength) { 60 | sb.append(' '); 61 | } 62 | line = sb.toString(); 63 | } 64 | return line; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/Tile.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared; 2 | 3 | import com.brackeen.scared.entity.Entity; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Tile { 9 | 10 | public static final int RENDER_STATE_MAX = (1 << 16); 11 | 12 | public static final int TYPE_NOTHING = 0; 13 | public static final int TYPE_WALL = 1; 14 | public static final int TYPE_DOOR = 2; //(subtype: key #) 15 | public static final int TYPE_WINDOW = 3; //(subtype: 1=west/east 2=north/south) 16 | public static final int TYPE_GENERATOR = 4; 17 | public static final int TYPE_MOVABLE_WALL = 5; 18 | public static final int TYPE_EXIT = 6; 19 | 20 | public int type; 21 | public int subtype; 22 | public int state; 23 | public int renderState; 24 | public int renderVisible; 25 | private SoftTexture texture; 26 | private List entities; 27 | 28 | /* Checks if the tile is solid for collision purposes. */ 29 | public boolean isSolid() { 30 | if (type == TYPE_DOOR) { 31 | return renderState < RENDER_STATE_MAX * 3 / 4; 32 | } else { 33 | return (type != TYPE_NOTHING); 34 | } 35 | } 36 | 37 | public List getEntities() { 38 | return entities; 39 | } 40 | 41 | public boolean hasEntities() { 42 | return (entities != null && entities.size() > 0); 43 | } 44 | 45 | public SoftTexture getTexture() { 46 | return texture; 47 | } 48 | 49 | public void setTexture(SoftTexture texture) { 50 | if (!texture.isPowerOfTwo()) { 51 | throw new IllegalArgumentException("Texture not a power of two"); 52 | } 53 | this.texture = texture; 54 | } 55 | 56 | public void addEntity(Entity entity) { 57 | if (entity.getTile() != null) { 58 | entity.getTile().removeEntity(entity); 59 | } 60 | if (entities == null) { 61 | entities = new ArrayList<>(); 62 | } 63 | entities.add(entity); 64 | entity.setTile(this); 65 | } 66 | 67 | public void removeEntity(Entity entity) { 68 | if (entity.getTile() == this) { 69 | entity.setTile(null); 70 | } 71 | if (entities != null) { 72 | entities.remove(entity); 73 | } 74 | } 75 | 76 | public int getDoorType() { 77 | if (type == TYPE_DOOR) { 78 | return subtype & 0x1f; 79 | } else { 80 | return 0; 81 | } 82 | } 83 | 84 | public boolean isDoorUnlocked() { 85 | return type == TYPE_DOOR && ((subtype & 0x1f) == 0 || (subtype & 0x20) != 0); 86 | } 87 | 88 | public void setDoorUnlocked(boolean unlocked) { 89 | if (type == TYPE_DOOR && (subtype & 0x1f) != 0) { 90 | if (unlocked) { 91 | subtype |= 0x20; 92 | } else { 93 | subtype &= ~0x20; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/action/Action.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.action; 2 | 3 | public interface Action { 4 | 5 | void tick(); 6 | 7 | void unload(); 8 | 9 | boolean isFinished(); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/action/DoorAction.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.action; 2 | 3 | import com.brackeen.scared.Map; 4 | import com.brackeen.scared.SoundPlayer3D; 5 | import com.brackeen.scared.Tile; 6 | import com.brackeen.scared.entity.Player; 7 | 8 | public class DoorAction implements Action { 9 | 10 | private static final int DONE = 0; 11 | private static final int OPENING = 1; 12 | private static final int OPEN = 2; 13 | private static final int CLOSING = 3; 14 | private static final int STAY_OPEN_FOREVER = 4; 15 | 16 | private static final int TICKS_TO_OPEN = 12; // Fast enough to not stop the player 17 | private static final int TICKS_TO_CLOSE = 24; 18 | private static final int TICKS_WAIT_BEFORE_CLOSING = 200; 19 | 20 | private final Map map; 21 | private final int x; 22 | private final int y; 23 | private final Tile tile; 24 | private int state; 25 | private int startRenderState; 26 | private int ticks; 27 | 28 | public DoorAction(Map map, int x, int y) { 29 | this.map = map; 30 | this.x = x; 31 | this.y = y; 32 | tile = map.getTileAt(x, y); 33 | 34 | setState(OPENING); 35 | if (tile.isDoorUnlocked()) { 36 | SoundPlayer3D.play("/sound/doorwoosh.wav", map.getPlayer(), x, y); 37 | } else { 38 | tile.setDoorUnlocked(true); 39 | SoundPlayer3D.play("/sound/door_unlock.wav", map.getPlayer(), x, y); 40 | ticks = -4; // Delay a bit before opening 41 | } 42 | } 43 | 44 | public int getTileX() { 45 | return x; 46 | } 47 | 48 | public int getTileY() { 49 | return y; 50 | } 51 | 52 | private void setState(int state) { 53 | this.state = state; 54 | tile.state = state; 55 | startRenderState = tile.renderState; 56 | ticks = 0; 57 | } 58 | 59 | @Override 60 | public void unload() { 61 | 62 | } 63 | 64 | @Override 65 | public boolean isFinished() { 66 | return (state == tile.state && (state == DONE || state == STAY_OPEN_FOREVER)); 67 | } 68 | 69 | @Override 70 | public void tick() { 71 | if (isFinished()) { 72 | return; 73 | } 74 | 75 | ticks++; 76 | 77 | if (state == OPENING && ticks == 0) { 78 | SoundPlayer3D.play("/sound/doorwoosh.wav", map.getPlayer(), x, y); 79 | } 80 | 81 | // State set outside of this handler 82 | if (state != tile.state) { 83 | setState(tile.state); 84 | } 85 | 86 | if (ticks < 0) { 87 | return; 88 | } 89 | 90 | switch (state) { 91 | case OPENING: 92 | tile.renderState = startRenderState + ticks * Tile.RENDER_STATE_MAX / TICKS_TO_OPEN; 93 | if (tile.renderState >= Tile.RENDER_STATE_MAX) { 94 | tile.renderState = Tile.RENDER_STATE_MAX; 95 | setState(OPEN); 96 | } 97 | break; 98 | 99 | case OPEN: 100 | if (ticks >= TICKS_WAIT_BEFORE_CLOSING) { 101 | if (shouldClose()) { 102 | setState(CLOSING); 103 | } 104 | } 105 | break; 106 | 107 | case CLOSING: 108 | if (!shouldClose()) { 109 | setState(OPENING); 110 | } else { 111 | if (tile.renderState == Tile.RENDER_STATE_MAX) { 112 | SoundPlayer3D.play("/sound/doorwoosh.wav", map.getPlayer(), x, y); 113 | } 114 | 115 | tile.renderState = startRenderState - ticks * Tile.RENDER_STATE_MAX / TICKS_TO_CLOSE; 116 | if (tile.renderState <= 0) { 117 | tile.renderState = 0; 118 | setState(DONE); 119 | SoundPlayer3D.play("/sound/doorclose.wav", map.getPlayer(), x, y); 120 | } 121 | } 122 | break; 123 | 124 | case STAY_OPEN_FOREVER: 125 | tile.renderState = 0; 126 | break; 127 | } 128 | } 129 | 130 | private boolean shouldClose() { 131 | if (tile.hasEntities()) { 132 | return false; 133 | } 134 | Player p = map.getPlayer(); 135 | float dx = Math.abs(p.getX() - (x + 0.5f)); 136 | float dy = Math.abs(p.getY() - (y + 0.5f)); 137 | return (dx >= 1.5f || dy >= 1.5f); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/action/GeneratorAction.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.action; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.app.audio.AudioBuffer; 5 | import com.brackeen.app.audio.AudioStream; 6 | import com.brackeen.scared.Map; 7 | import com.brackeen.scared.SoundPlayer3D; 8 | import com.brackeen.scared.entity.Player; 9 | 10 | public class GeneratorAction implements Action { 11 | 12 | private final Player player; 13 | private final int sourceTileX; 14 | private final int sourceTileY; 15 | private AudioStream stream; 16 | 17 | public GeneratorAction(Map map, int x, int y) { 18 | this.player = map.getPlayer(); 19 | this.sourceTileX = x; 20 | this.sourceTileY = y; 21 | AudioBuffer audioBuffer = App.getApp().getAudio("/sound/bigfan.wav"); 22 | 23 | float volume = SoundPlayer3D.getVolume(player, sourceTileX, sourceTileY); 24 | float pan = SoundPlayer3D.getPan(player, sourceTileX, sourceTileY); 25 | stream = audioBuffer.play(volume, pan, true); 26 | } 27 | 28 | @Override 29 | public void tick() { 30 | if (stream != null) { 31 | stream.setVolume(SoundPlayer3D.getVolume(player, sourceTileX, sourceTileY)); 32 | stream.setPan(SoundPlayer3D.getPan(player, sourceTileX, sourceTileY)); 33 | } 34 | } 35 | 36 | @Override 37 | public void unload() { 38 | if (stream != null) { 39 | stream.stop(); 40 | stream = null; 41 | } 42 | } 43 | 44 | @Override 45 | public boolean isFinished() { 46 | return stream == null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/action/MovableWallAction.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.action; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.Map; 5 | import com.brackeen.scared.SoftTexture; 6 | import com.brackeen.scared.Tile; 7 | 8 | public class MovableWallAction implements Action { 9 | 10 | public static final int STATE_DONE = 0; 11 | private static final int STATE_MOVING = 1; 12 | 13 | private static final int TICKS_PER_TILE_MOVE = 60; 14 | 15 | private final Map map; 16 | private int x; 17 | private int y; 18 | private final int dx; 19 | private final int dy; 20 | private Tile tile; 21 | private final SoftTexture floorTexture; 22 | private int index; 23 | private int ticks; 24 | 25 | public MovableWallAction(Map map, int x, int y) { 26 | int playerTileX = (int) map.getPlayer().getX(); 27 | int playerTileY = (int) map.getPlayer().getY(); 28 | 29 | this.map = map; 30 | this.x = x; 31 | this.y = y; 32 | this.dx = x - playerTileX; 33 | this.dy = y - playerTileY; 34 | 35 | Tile playerTile = map.getTileAt(playerTileX, playerTileY); 36 | tile = map.getTileAt(x, y); 37 | tile.state = STATE_MOVING; 38 | 39 | map.setDefaultFloorTexture(playerTile.getTexture()); 40 | floorTexture = playerTile.getTexture(); 41 | 42 | App.getApp().getAudio("/sound/wallmove.wav").play(); 43 | index = 0; 44 | ticks = 0; 45 | } 46 | 47 | @Override 48 | public void unload() { 49 | // Do nothing 50 | } 51 | 52 | @Override 53 | public boolean isFinished() { 54 | return (index > 2); 55 | } 56 | 57 | @Override 58 | public void tick() { 59 | if (isFinished()) { 60 | return; 61 | } 62 | 63 | if (ticks < TICKS_PER_TILE_MOVE) { 64 | ticks++; 65 | tile.renderState = Tile.RENDER_STATE_MAX * ticks / TICKS_PER_TILE_MOVE; 66 | } else { 67 | index++; 68 | SoftTexture texture = tile.getTexture(); 69 | tile.setTexture(floorTexture); 70 | tile.type = 0; 71 | tile.subtype = 0; 72 | tile.state = STATE_DONE; 73 | tile.renderState = 0; 74 | 75 | x += dx; 76 | y += dy; 77 | 78 | tile = map.getTileAt(x, y); 79 | tile.setTexture(texture); 80 | tile.type = Tile.TYPE_MOVABLE_WALL; 81 | tile.subtype = 0; 82 | tile.state = STATE_MOVING; 83 | tile.renderState = 0; 84 | 85 | if (index == 2) { 86 | index = 3; 87 | tile.type = Tile.TYPE_WALL; 88 | tile.state = STATE_DONE; 89 | tile.renderState = 0; 90 | } 91 | ticks = 0; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/Ammo.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.Map; 5 | import com.brackeen.scared.SoftTexture; 6 | 7 | public class Ammo extends Entity { 8 | 9 | private final Map map; 10 | 11 | public Ammo(Map map, SoftTexture texture, float x, float y) { 12 | super(0.25f, x, y); 13 | this.map = map; 14 | setTexture(texture); 15 | } 16 | 17 | @Override 18 | public void notifyPlayerCollision(Player player) { 19 | int ammo = player.getAmmo(); 20 | if (ammo < Player.MAX_AMMO) { 21 | App.getApp().getAudio("/sound/getammo.wav").play(); 22 | 23 | map.setMessage("You got some ammo"); 24 | player.setAmmo(Math.min(ammo + 20, Player.MAX_AMMO)); 25 | 26 | delete(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/BlastMark.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.scared.SoftTexture; 4 | 5 | public class BlastMark extends Entity { 6 | 7 | private final SoftTexture[] textures; 8 | private int countdown; 9 | 10 | public BlastMark(SoftTexture[] textures, float x, float y, int countdown) { 11 | super(0, x, y); 12 | this.textures = textures; 13 | this.countdown = countdown; 14 | setTexture(textures[0]); 15 | setZ(0.5f - getTexture().getHeight() / 2 * getTextureScale()); 16 | } 17 | 18 | @Override 19 | public void tick() { 20 | if ((countdown % 2) == 0) { 21 | int index = (int) (Math.random() * textures.length); 22 | index = Math.min(index, textures.length - 1); 23 | setTexture(textures[index]); 24 | } 25 | countdown--; 26 | if (countdown <= 0) { 27 | delete(); 28 | } 29 | } 30 | 31 | @Override 32 | public void setDistanceFromCamera(float distanceFromCamera) { 33 | // Bring it forward a bit so that it appears in front of walls 34 | float extraDist = getTexture().getWidth() * getTextureScale(); 35 | super.setDistanceFromCamera(distanceFromCamera - extraDist); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/Enemy.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.app.audio.AudioBuffer; 5 | import com.brackeen.scared.Map; 6 | import com.brackeen.scared.SoftTexture; 7 | import com.brackeen.scared.SoundPlayer3D; 8 | import com.brackeen.scared.Stats; 9 | 10 | import java.awt.geom.Point2D; 11 | import java.util.List; 12 | 13 | public class Enemy extends Entity { 14 | 15 | public static final int NUM_IMAGES = 15; 16 | 17 | private static final int STATE_ASLEEP = 0; 18 | private static final int STATE_TERMINATE = 1; 19 | private static final int STATE_MOVE_LEFT = 2; 20 | private static final int STATE_MOVE_FAR_LEFT = 3; 21 | private static final int STATE_MOVE_RIGHT = 4; 22 | private static final int STATE_MOVE_FAR_RIGHT = 5; 23 | private static final int LAST_STATE_WITH_ANIM = STATE_MOVE_FAR_RIGHT; 24 | private static final int STATE_READY = 6; 25 | private static final int STATE_AIM = 7; 26 | private static final int STATE_FIRE = 8; 27 | private static final int STATE_HURT = 9; 28 | private static final int STATE_DYING = 10; 29 | private static final int STATE_DEAD = 11; 30 | 31 | // @formatter:off 32 | // state = 0 1 2 3 4 5 6 7 8 9 10 11 33 | private static final int[] STATE_TEXTURE = { 0, 0, 2, 4, 6, 8, 0, 10, 11, 12, 13, 14 }; 34 | private static final int[] STATE_TICKS = { 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 }; 35 | // @formatter:on 36 | 37 | private static final float STEP_SIZE = 0.05f; 38 | 39 | private final SoftTexture[] textures; 40 | private final Map map; 41 | private final Stats stats; 42 | private int state; 43 | private int health; 44 | private final double p; //probability of changing states 45 | private int ticksRemaining; 46 | private int ticks; 47 | private double aimAngle; 48 | 49 | private boolean playerVisibilityNeedsCalculation; 50 | private boolean isPlayerVisible; 51 | 52 | public Enemy(Map map, Stats stats, SoftTexture[] textures, float x, float y, int type) { 53 | super(0.25f, x, y); 54 | this.textures = textures; 55 | this.map = map; 56 | this.stats = stats; 57 | setTexture(textures[0]); 58 | //setTextureScale(getTextureScale() / 2); // For 128x128 textures 59 | setZ(-4f / DEFAULT_PIXELS_PER_TILE); 60 | setState(STATE_ASLEEP); 61 | 62 | switch (type) { 63 | case 1: 64 | default: 65 | health = 20; 66 | STATE_TICKS[STATE_READY] = 18; 67 | STATE_TICKS[STATE_AIM] = 40; 68 | p = .1; 69 | break; 70 | 71 | case 2: 72 | health = 30; 73 | STATE_TICKS[STATE_READY] = 12; 74 | STATE_TICKS[STATE_AIM] = 24; 75 | p = .05; 76 | break; 77 | 78 | case 3: 79 | health = 50; 80 | STATE_TICKS[STATE_READY] = 6; 81 | STATE_TICKS[STATE_AIM] = 18; 82 | p = .1; 83 | break; 84 | 85 | case 4: 86 | health = 80; 87 | STATE_TICKS[STATE_READY] = 0; 88 | STATE_TICKS[STATE_AIM] = 12; 89 | p = .03; 90 | break; 91 | } 92 | } 93 | 94 | private void setState(int state) { 95 | if (this.state != state) { 96 | this.state = state; 97 | if (state == STATE_DEAD) { 98 | setRadius(0); // Prevent future collisions 99 | } 100 | ticksRemaining = STATE_TICKS[state]; 101 | } 102 | } 103 | 104 | public boolean hurt(int points) { 105 | if (health <= 0) { 106 | return false; 107 | } else { 108 | health -= points; 109 | boolean gotoHurtState = false; 110 | 111 | if (health <= 0) { 112 | gotoHurtState = true; 113 | } else if (state == STATE_FIRE) { 114 | // 50% of interrupting firing 115 | if (Math.random() < .5) { 116 | gotoHurtState = true; 117 | } 118 | } else if (state != STATE_HURT) { 119 | gotoHurtState = true; 120 | } 121 | 122 | if (gotoHurtState) { 123 | setState(STATE_HURT); 124 | } 125 | return true; 126 | } 127 | } 128 | 129 | @Override 130 | public boolean notifyCollision(Entity movingEntity) { 131 | return true; 132 | } 133 | 134 | private boolean isPlayerVisible(float angleToPlayer) { 135 | if (playerVisibilityNeedsCalculation) { 136 | playerVisibilityNeedsCalculation = false; 137 | isPlayerVisible = false; 138 | Point2D.Float point = map.getWallCollision(getX(), getY(), (float) Math.toDegrees(angleToPlayer)); 139 | if (point != null) { 140 | List playerHit = map.getCollisions(Player.class, getX(), getY(), point.x, point.y); 141 | if (playerHit.size() > 0) { 142 | isPlayerVisible = true; 143 | } 144 | } 145 | } 146 | return isPlayerVisible; 147 | } 148 | 149 | @Override 150 | public void tick() { 151 | playerVisibilityNeedsCalculation = true; 152 | Player player = map.getPlayer(); 153 | 154 | float stepx = 0; 155 | float stepy = 0; 156 | float dx = player.getX() - getX(); 157 | float dy = player.getY() - getY(); 158 | float angleToPlayer = (float) Math.atan2(dy, dx); 159 | 160 | if ((ticksRemaining <= 0 || state == STATE_TERMINATE) && Math.abs(dx) < 2f && Math.abs(dy) < 2f && state < STATE_READY) { 161 | // Player is very close - move immediately or fire 162 | double pq = Math.random(); 163 | 164 | if (pq < 0.25f) { 165 | setState(STATE_MOVE_FAR_LEFT); 166 | } else if (pq < 0.50f) { 167 | setState(STATE_MOVE_FAR_RIGHT); 168 | } else { 169 | setState(STATE_READY); 170 | } 171 | } else if (state > STATE_ASLEEP && state < STATE_READY && Math.random() < p) { 172 | // When moving, randomly change to another move state 173 | int s = (int) Math.round(Math.random() * 6); 174 | switch (s) { 175 | case 0: 176 | default: 177 | setState(STATE_TERMINATE); 178 | break; 179 | case 1: 180 | setState(STATE_MOVE_LEFT); 181 | break; 182 | case 2: 183 | setState(STATE_MOVE_RIGHT); 184 | break; 185 | case 3: 186 | setState(STATE_MOVE_FAR_LEFT); 187 | break; 188 | case 4: 189 | setState(STATE_MOVE_FAR_RIGHT); 190 | break; 191 | case 5: 192 | if (isPlayerVisible(angleToPlayer)) { 193 | setState(STATE_READY); 194 | } else { 195 | setState(STATE_TERMINATE); 196 | } 197 | break; 198 | } 199 | } 200 | 201 | switch (state) { 202 | case STATE_ASLEEP: 203 | if (isPlayerVisible(angleToPlayer)) { 204 | setState(STATE_TERMINATE); 205 | } 206 | break; 207 | 208 | case STATE_TERMINATE: 209 | stepx = (float) Math.cos(angleToPlayer) * STEP_SIZE; 210 | stepy = (float) Math.sin(angleToPlayer) * STEP_SIZE; 211 | break; 212 | 213 | case STATE_MOVE_LEFT: 214 | stepx = (float) Math.cos(angleToPlayer + Math.PI / 4) * STEP_SIZE; 215 | stepy = (float) Math.sin(angleToPlayer + Math.PI / 4) * STEP_SIZE; 216 | break; 217 | 218 | case STATE_MOVE_RIGHT: 219 | stepx = (float) Math.cos(angleToPlayer - Math.PI / 4) * STEP_SIZE; 220 | stepy = (float) Math.sin(angleToPlayer - Math.PI / 4) * STEP_SIZE; 221 | break; 222 | 223 | case STATE_MOVE_FAR_LEFT: 224 | stepx = (float) Math.cos(angleToPlayer + Math.PI / 2) * STEP_SIZE; 225 | stepy = (float) Math.sin(angleToPlayer + Math.PI / 2) * STEP_SIZE; 226 | break; 227 | 228 | case STATE_MOVE_FAR_RIGHT: 229 | stepx = (float) Math.cos(angleToPlayer - Math.PI / 2) * STEP_SIZE; 230 | stepy = (float) Math.sin(angleToPlayer - Math.PI / 2) * STEP_SIZE; 231 | break; 232 | 233 | case STATE_READY: 234 | if (ticksRemaining <= 0) { 235 | setState(STATE_AIM); 236 | } 237 | break; 238 | 239 | case STATE_AIM: 240 | if (ticksRemaining <= 0) { 241 | if (player.isAlive() && isPlayerVisible(angleToPlayer)) { 242 | aimAngle = angleToPlayer; 243 | setState(STATE_FIRE); 244 | } else { 245 | setState(STATE_TERMINATE); 246 | } 247 | } 248 | break; 249 | 250 | case STATE_FIRE: 251 | if (player.isFreezeEnemies()) { 252 | setState(STATE_TERMINATE); 253 | } else if (ticksRemaining <= 0) { 254 | playSound3D("/sound/laser0.wav", 1.5f, 0.5f); 255 | stats.numEnemyShotsFired++; 256 | 257 | // fire shot 258 | if (isPlayerVisible(angleToPlayer)) { 259 | Point2D.Float point = map.getWallCollision(getX(), getY(), (float) Math.toDegrees(aimAngle)); 260 | if (point != null) { 261 | List playerHit = map.getCollisions(Player.class, getX(), getY(), point.x, point.y); 262 | if (playerHit.size() > 0) { 263 | // here, diffAngle is the differnce between the angle the 264 | // robot aimed at and the angle the player is currently at 265 | double diffAngle = Math.abs(aimAngle - angleToPlayer); 266 | int hitPoints = 0; 267 | if (diffAngle < .04) { // about 2.3 degrees 268 | hitPoints = 15 + (int) Math.round(Math.random() * 7); 269 | } else if (diffAngle < .25) { // about 15 degrees 270 | hitPoints = 3 + (int) Math.round(Math.random() * 5); 271 | } 272 | 273 | boolean actuallyHurt = player.hurt(hitPoints); 274 | if (actuallyHurt) { 275 | stats.numEnemyShotsFiredHit++; 276 | } 277 | } 278 | } 279 | } 280 | 281 | setState(STATE_TERMINATE); 282 | } 283 | 284 | break; 285 | 286 | case STATE_HURT: 287 | if (ticksRemaining <= 0 || health <= 0) { 288 | if (health <= 0) { 289 | playSound3D("/sound/enemy_dead.wav", 1.5f, 0.4f); 290 | setState(STATE_DYING); 291 | } else if (Math.random() < .666) { 292 | setState(STATE_TERMINATE); 293 | } else { 294 | setState(STATE_ASLEEP); 295 | // immediate fire 296 | aimAngle = angleToPlayer; 297 | setState(STATE_FIRE); 298 | } 299 | } 300 | break; 301 | 302 | case STATE_DYING: 303 | if (ticksRemaining <= 0) { 304 | setState(STATE_DEAD); 305 | player.setKills(player.getKills() + 1); 306 | } 307 | break; 308 | } 309 | 310 | if (!player.isFreezeEnemies()) { 311 | float newX = getX() + stepx; 312 | float newY = getY() + stepy; 313 | 314 | if (!isCollision(newX, newY)) { 315 | setLocation(newX, newY); 316 | } else if (!isCollision(newX, getY())) { 317 | setX(newX); 318 | } else if (!isCollision(getX(), newY)) { 319 | setY(newY); 320 | } 321 | } 322 | 323 | ticksRemaining--; 324 | ticks++; 325 | int textureIndex = STATE_TEXTURE[state]; 326 | if (state <= LAST_STATE_WITH_ANIM && ((ticks / 12) & 1) == 0) { 327 | textureIndex++; 328 | } 329 | setTexture(textures[textureIndex]); 330 | } 331 | 332 | private boolean isCollision(float x, float y) { 333 | int minTileX = (int) (x - getRadius()); 334 | int maxTileX = (int) (x + getRadius()); 335 | int minTileY = (int) (y - getRadius()); 336 | int maxTileY = (int) (y + getRadius()); 337 | 338 | for (int tileY = minTileY; tileY <= maxTileY; tileY++) { 339 | for (int tileX = minTileX; tileX <= maxTileX; tileX++) { 340 | if (map.isSolidAt(tileX, tileY)) { 341 | return true; 342 | } 343 | } 344 | } 345 | 346 | return false; 347 | } 348 | 349 | private void playSound3D(String audioName, float gain, float minVolume) { 350 | AudioBuffer audio = App.getApp().getAudio(audioName); 351 | if (audio != null) { 352 | Entity listener = map.getPlayer(); 353 | Entity source = this; 354 | audio.play(Math.max(minVolume, SoundPlayer3D.getVolume(listener, source) * gain), 355 | SoundPlayer3D.getPan(listener, source), 356 | false); 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/Entity.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.scared.SoftTexture; 4 | import com.brackeen.scared.Tile; 5 | 6 | public class Entity implements Comparable { 7 | 8 | public static final int DEFAULT_PIXELS_PER_TILE = 64; 9 | 10 | private Tile tile; 11 | private float radius; 12 | private float x; 13 | private float y; 14 | private float z; 15 | private float distanceFromCamera; 16 | private boolean deleted; 17 | private float direction; // degrees 18 | private SoftTexture texture; 19 | private float textureScale = 1f / DEFAULT_PIXELS_PER_TILE; 20 | 21 | public Entity(float radius, float x, float y) { 22 | setRadius(radius); 23 | setLocation(x, y); 24 | } 25 | 26 | public Tile getTile() { 27 | return tile; 28 | } 29 | 30 | public void setTile(Tile tile) { 31 | this.tile = tile; 32 | } 33 | 34 | public SoftTexture getTexture() { 35 | return texture; 36 | } 37 | 38 | public void setTexture(SoftTexture texture) { 39 | this.texture = texture; 40 | } 41 | 42 | public float getTextureScale() { 43 | return textureScale; 44 | } 45 | 46 | public void setTextureScale(float textureScale) { 47 | this.textureScale = textureScale; 48 | } 49 | 50 | public float getDistanceFromCamera() { 51 | return distanceFromCamera; 52 | } 53 | 54 | public void setDistanceFromCamera(float distanceFromCamera) { 55 | this.distanceFromCamera = distanceFromCamera; 56 | } 57 | 58 | public float getRadius() { 59 | return radius; 60 | } 61 | 62 | public void setRadius(float radius) { 63 | this.radius = radius; 64 | } 65 | 66 | public float getX() { 67 | return x; 68 | } 69 | 70 | public float getY() { 71 | return y; 72 | } 73 | 74 | public float getZ() { 75 | return z; 76 | } 77 | 78 | public void setX(float x) { 79 | this.x = x; 80 | } 81 | 82 | public void setY(float y) { 83 | this.y = y; 84 | } 85 | 86 | public void setZ(float z) { 87 | this.z = z; 88 | } 89 | 90 | public void setLocation(float x, float y) { 91 | this.x = x; 92 | this.y = y; 93 | } 94 | 95 | public float getDirection() { 96 | return direction; 97 | } 98 | 99 | public void setDirection(float direction) { 100 | this.direction = direction; 101 | } 102 | 103 | public void tick() { 104 | 105 | } 106 | 107 | public boolean isDeleted() { 108 | return deleted; 109 | } 110 | 111 | public void delete() { 112 | deleted = true; 113 | } 114 | 115 | public boolean onCollisionWithEntityShouldSlide() { 116 | return false; 117 | } 118 | 119 | public boolean onCollisionWithWallShouldSlide() { 120 | return false; 121 | } 122 | 123 | public void notifyPlayerCollision(Player player) { 124 | // Do nothing 125 | } 126 | 127 | /* 128 | Notify that a moving entity collided with this entity. 129 | Returns true if the moving entity should stop. 130 | */ 131 | public boolean notifyCollision(Entity movingEntity) { 132 | if (this instanceof Player) { 133 | movingEntity.notifyPlayerCollision((Player) this); 134 | } else if (movingEntity instanceof Player) { 135 | this.notifyPlayerCollision((Player) movingEntity); 136 | } 137 | return false; 138 | } 139 | 140 | // Sort back-to-front 141 | public int compareTo(Entity t) { 142 | if (distanceFromCamera < t.distanceFromCamera) { 143 | return 1; 144 | } else if (distanceFromCamera > t.distanceFromCamera) { 145 | return -1; 146 | } 147 | return 0; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/Key.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.Map; 5 | import com.brackeen.scared.SoftTexture; 6 | 7 | public class Key extends Entity { 8 | 9 | public static final int NUM_KEYS = 3; 10 | 11 | private static final String[] KEY_COLORS = {"", "RED", "GREEN", "BLUE"}; 12 | 13 | private final Map map; 14 | private final int type; 15 | 16 | public Key(Map map, SoftTexture texture, float x, float y, int type) { 17 | super(0.25f, x, y); 18 | this.map = map; 19 | this.type = type; 20 | setTexture(texture); 21 | } 22 | 23 | @Override 24 | public void notifyPlayerCollision(Player player) { 25 | App.getApp().getAudio("/sound/unlock.wav").play(); 26 | map.setMessage("You got the " + KEY_COLORS[type] + " key"); 27 | player.addKey(type); 28 | delete(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/MedKit.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.Map; 5 | import com.brackeen.scared.SoftTexture; 6 | 7 | public class MedKit extends Entity { 8 | 9 | private final Map map; 10 | private final boolean nuclear; 11 | 12 | public MedKit(Map map, SoftTexture texture, float x, float y, boolean nuclear) { 13 | super(0.25f, x, y); 14 | this.map = map; 15 | this.nuclear = nuclear; 16 | setTexture(texture); 17 | } 18 | 19 | @Override 20 | public void notifyPlayerCollision(Player player) { 21 | int health = player.getHealth(); 22 | if (!nuclear && health < Player.MAX_HEALTH) { 23 | map.setMessage("You got a med kit"); 24 | App.getApp().getAudio("/sound/getammo.wav").play(); 25 | player.setHealth(Math.min(health + 20, Player.MAX_HEALTH)); 26 | delete(); 27 | } else if (nuclear && player.getHealth() < Player.MAX_NUCLEAR_HEALTH) { 28 | map.setMessage("N*U*C*L*E*A*R"); 29 | App.getApp().getAudio("/sound/nuclear_health.wav").play(); 30 | player.setHealth(Player.MAX_NUCLEAR_HEALTH); 31 | delete(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/brackeen/scared/entity/Player.java: -------------------------------------------------------------------------------- 1 | package com.brackeen.scared.entity; 2 | 3 | import com.brackeen.app.App; 4 | import com.brackeen.scared.Map; 5 | import com.brackeen.scared.Tile; 6 | 7 | public class Player extends Entity { 8 | 9 | public static final int MAX_AMMO = 100; 10 | public static final int MAX_HEALTH = 100; 11 | public static final int MAX_NUCLEAR_HEALTH = 200; 12 | public static final int DEFAULT_AMMO = 20; 13 | public static final int DEFAULT_HEALTH = MAX_HEALTH; 14 | 15 | private final Map map; 16 | private int health = DEFAULT_HEALTH; 17 | private int ammo = DEFAULT_AMMO; 18 | 19 | private int keys = 1; 20 | private int kills = 0; 21 | private int secrets = 0; 22 | private int hitWarningTicksRemaining = 0; 23 | 24 | private boolean godMode = false; 25 | private boolean freezeEnemies = false; 26 | private boolean isAlive = true; 27 | 28 | public Player(Map map) { 29 | super(0.25f, 0, 0); 30 | this.map = map; 31 | setZ(0.5f); 32 | } 33 | 34 | public boolean isGodMode() { 35 | return godMode; 36 | } 37 | 38 | public void setGodMode(boolean godMode) { 39 | this.godMode = godMode; 40 | } 41 | 42 | public boolean isFreezeEnemies() { 43 | return freezeEnemies; 44 | } 45 | 46 | public void setFreezeEnemies(boolean freezeEnemies) { 47 | this.freezeEnemies = freezeEnemies; 48 | } 49 | 50 | public int getAmmo() { 51 | return ammo; 52 | } 53 | 54 | public void setAmmo(int ammo) { 55 | this.ammo = ammo; 56 | } 57 | 58 | public int getHealth() { 59 | return health; 60 | } 61 | 62 | public void setHealth(int health) { 63 | this.health = health; 64 | } 65 | 66 | public int getKills() { 67 | return kills; 68 | } 69 | 70 | public void setKills(int kills) { 71 | this.kills = kills; 72 | } 73 | 74 | public int getSecrets() { 75 | return secrets; 76 | } 77 | 78 | public void setSecrets(int secrets) { 79 | this.secrets = secrets; 80 | } 81 | 82 | public boolean wasHitRecently() { 83 | return hitWarningTicksRemaining > 0; 84 | } 85 | 86 | @Override 87 | public void setTile(Tile tile) { 88 | if (tile != null && tile != getTile()) { 89 | map.notifyPlayerEnteredTile((int) getX(), (int) getY()); 90 | } 91 | super.setTile(tile); 92 | } 93 | 94 | public boolean hurt(int points) { 95 | if (godMode || !isAlive() || points <= 0) { 96 | return false; 97 | } else { 98 | hitWarningTicksRemaining = 12; 99 | health -= points; 100 | if (health <= 0) { 101 | health = 0; 102 | isAlive = false; 103 | App.getApp().getAudio("/sound/player_dead.wav").play(); 104 | } else if (points > 15) { 105 | App.getApp().getAudio("/sound/player_hurt.wav").play(); 106 | } 107 | return true; 108 | } 109 | } 110 | 111 | @Override 112 | public void tick() { 113 | if (hitWarningTicksRemaining > 0) { 114 | hitWarningTicksRemaining--; 115 | } 116 | } 117 | 118 | public boolean isAlive() { 119 | return isAlive; 120 | } 121 | 122 | public void setAlive(boolean isAlive) { 123 | this.isAlive = isAlive; 124 | } 125 | 126 | @Override 127 | public boolean onCollisionWithEntityShouldSlide() { 128 | return true; 129 | } 130 | 131 | @Override 132 | public boolean onCollisionWithWallShouldSlide() { 133 | return true; 134 | } 135 | 136 | public boolean hasKey(int key) { 137 | return (keys & (1 << key)) != 0; 138 | } 139 | 140 | public void addKey(int key) { 141 | keys |= (1 << key); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/non-packaged-resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Scared 4 | 5 | 6 | 7 | 8 | 9 | 10 |

Requires Java. Click to focus.

11 |

Within the game, press ESC for the console.

12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/background/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/background/background.png -------------------------------------------------------------------------------- /src/main/resources/enemy/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/0.png -------------------------------------------------------------------------------- /src/main/resources/enemy/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/1.png -------------------------------------------------------------------------------- /src/main/resources/enemy/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/10.png -------------------------------------------------------------------------------- /src/main/resources/enemy/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/11.png -------------------------------------------------------------------------------- /src/main/resources/enemy/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/12.png -------------------------------------------------------------------------------- /src/main/resources/enemy/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/13.png -------------------------------------------------------------------------------- /src/main/resources/enemy/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/14.png -------------------------------------------------------------------------------- /src/main/resources/enemy/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/2.png -------------------------------------------------------------------------------- /src/main/resources/enemy/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/3.png -------------------------------------------------------------------------------- /src/main/resources/enemy/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/4.png -------------------------------------------------------------------------------- /src/main/resources/enemy/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/5.png -------------------------------------------------------------------------------- /src/main/resources/enemy/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/6.png -------------------------------------------------------------------------------- /src/main/resources/enemy/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/7.png -------------------------------------------------------------------------------- /src/main/resources/enemy/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/8.png -------------------------------------------------------------------------------- /src/main/resources/enemy/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/enemy/9.png -------------------------------------------------------------------------------- /src/main/resources/hud/crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/hud/crosshair.png -------------------------------------------------------------------------------- /src/main/resources/hud/gun01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/hud/gun01.png -------------------------------------------------------------------------------- /src/main/resources/hud/gun02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/hud/gun02.png -------------------------------------------------------------------------------- /src/main/resources/maps/level0.txt: -------------------------------------------------------------------------------- 1 | w=16 2 | h=16 3 | dir=330 4 | ################ 5 | # ## m# 6 | # ^ A ^ # 7 | # ## # 8 | # ######### # 9 | ##A### # 10 | ## ### # 11 | # ^# ^ #### # 12 | # ^ # # # # 13 | X # #m A # 14 | # # # # # 15 | ###### #### # 16 | #S # #^ # # 17 | # A #h A # 18 | # # # # # 19 | ################ 20 | 21 | 66666EE5EE5EE5EE 22 | 60006E000000000E 23 | 6000D0000000D00E 24 | 60006E0000000005 25 | 600D6EE5EE5EE00E 26 | 66066E000000000E 27 | EE0EEE000000000E 28 | E00005000E5EE005 29 | E0000E000E0DE00E 30 | 00000E00DE00000F 31 | E00005000E00E00E 32 | EEE5EE000E5EE005 33 | E0000E000E00E00E 34 | E00000000E00000F 35 | E0000E000E00E00E 36 | EEE5EEE5EE5EEEEE 37 | -------------------------------------------------------------------------------- /src/main/resources/maps/level1.txt: -------------------------------------------------------------------------------- 1 | w=20 2 | h=20 3 | dir=90 4 | ############X####### 5 | # # # # 6 | # b # # ^ ^ # 7 | # ^ # ^ # # 8 | # # # ^ # 9 | ##A## ########## # 10 | # | # # 11 | # | ^ # # 12 | ##### ### ###### B # 13 | # # # # #^ ^ # ### 14 | # # #m# # ^ # ### 15 | #^#h# # # #h # #h# 16 | # ### #A#B####A###h# 17 | # #m# 18 | # #m# 19 | # ### ### ###### # # 20 | #^#m# #^# # # # # 21 | # # # # # ^ # # 22 | # # A #S# @ 23 | #################### 24 | 25 | 777C7777EEFEEEFEE5EE 26 | C3337333E9999999999E 27 | 73237333599999999995 28 | 73337333E9999999999E 29 | 73337333E9999999999E 30 | 77377333EEE5EE5EE595 31 | 73333333333333333E9E 32 | 73333333333333333E9E 33 | 777C737773777C77339E 34 | C3337373737333373EEE 35 | 73337373737333373777 36 | 73737373737333373737 37 | 737C7373737C773C7737 38 | C3333333333333333737 39 | 73333333333333333737 40 | 7377737C73777C773737 41 | 73737373737333373737 42 | 7333C373737333333737 43 | C3337333737333333B33 44 | 77C77777777C777C7777 45 | -------------------------------------------------------------------------------- /src/main/resources/maps/level2.txt: -------------------------------------------------------------------------------- 1 | w=24 2 | h=24 3 | dir=345 4 | ################ 5 | #S ######### 6 | # ######## ## ^ # 7 | # #m^ A A ^ # 8 | # #m # ## # 9 | # ######## ###### # 10 | # ###### # 11 | ################ # # 12 | ###############^ A # 13 | #h ^ A ^## # # 14 | #h ## ####### # 15 | #mb ^ ## ## # # 16 | #m ^## ## h A # 17 | ######### ^## # # 18 | ########### ######## # 19 | # # ^####### # 20 | # ^ B ^ # 21 | # # ^ # 22 | # ^ ##@############## 23 | # ^## #----# h#### 24 | # ####### @ A ^# 25 | # ###############B#### # 26 | # A ^ # # # 27 | ################### #X# 28 | 29 | EEE5EEE5EEE5EEEE00000000 30 | E00000000000000577C77777 31 | 500006666666600E70000007 32 | E00006444444000000000007 33 | E00006444444600E70D00007 34 | 500006666666600577C77007 35 | E0000000D000000E5E5EC00C 36 | EEE5EEE5EEE5EEEE99997007 37 | 0AAAAAAAA777777F99990007 38 | 0A9999990000007E9999B007 39 | 0A999999A700007E5E5E7007 40 | 0A999999AC0000CE99997007 41 | 0A999999A700007F99990007 42 | 0AAAAAAAA700D07E9999B007 43 | 777C77777770077E5E5EC00C 44 | 700000070000007C77777007 45 | 7000D0000000000000000007 46 | C00000070000000000000007 47 | 70000007777C777777C77777 48 | B00000077111111170007EEE 49 | 707C7777111111117000022E 50 | 7071111111111111107C7E2E 51 | 700111111111111111100E2E 52 | 777111111111111111100EEE 53 | -------------------------------------------------------------------------------- /src/main/resources/maps/level3.txt: -------------------------------------------------------------------------------- 1 | w=24 2 | h=24 3 | dir=270 4 | ######################## 5 | #h B ## h# # d#hmm# 6 | # # ## # S # # # 7 | # # ^## # # # # 8 | # ^# ##^ # # ^# ^ # 9 | # # ## # # # # 10 | # ^ # ## # # # # 11 | # #^ X#^ A A # # 12 | # # ## # # #^ # 13 | # ######### ######A## 14 | # ^ # # # # # 15 | # # # # # ^ # 16 | # # # ^ # # #*#^ # 17 | # m#### # # ### # 18 | # ###### b# # # 19 | # # # # # #### 20 | # #^ A # # # # A # 21 | # # ### #^# #m# # # 22 | # ^# ######## ### #h # 23 | # # # # ^# 24 | # # A #### 25 | # ###########D###### H# 26 | # ^ ^ ^ m@ # 27 | ######################## 28 | 29 | EE5EEEEEE11111111111E5EE 30 | E0022222E13313331331000E 31 | 50020502E13313331331000E 32 | E2220E02E133133313310005 33 | E2000E22513313331331000E 34 | E2220E20E13313331331000E 35 | 50020522E13313331331000E 36 | E0220E02E133333333310005 37 | E0200E00E13313331331000E 38 | E0222EEEE11111311111E3EE 39 | 50002533E333313133333335 40 | E0222E33E33331313333333E 41 | E0200E33E333313133EEE33E 42 | E0200EEEE333313133EEE335 43 | 500AAAAA533331313333333E 44 | E00A0000E33331313333E5EE 45 | E00A0000333131313133300E 46 | E00A00AAE33131313133E00E 47 | 500A00AA5111113111335005 48 | E00A0000E33333333333E00E 49 | E00A0000333333333333EEEE 50 | E00AAAAAE5EE5E3E5EE5E005 51 | 50000000000000000000E00E 52 | E5EEE5EEE5EE5EEE5EE5E5EE 53 | -------------------------------------------------------------------------------- /src/main/resources/maps/level4.txt: -------------------------------------------------------------------------------- 1 | w=32 2 | h=32 3 | dir=280 4 | ################################ 5 | # # #### ## ^ # 6 | # S # ^ ^ @ # ^ ## # 7 | # # # # ## ##B## 8 | ##A###### # # ## # # 9 | # # ####### ^ ## # c # 10 | # #-# # # ^ ##### # h# 11 | # m| # #md ^ #####B###### 12 | # #-# # #h ##m # ^# 13 | # # ##########h C ^# 14 | # # ## ####### # ^# 15 | # #-# # ## # ##C## 16 | # m| ##A###@#### # # # 17 | # #-# # #mh#^ # X 18 | # # #-# ###### #^^^# 19 | # # | A @ # ##### 20 | # # #-# # # # ^##### 21 | # #^ # # #^ m# 22 | # # ^ # #mh#^ h# 23 | ####A###### #-# # ######## # 24 | #^ | # # # # 25 | # #-# # # #### # # 26 | # ^ # #^ # # # 27 | ####D############## #### # # # 28 | # # hh#^ # # # # 29 | # # # ##### # #^# # 30 | # # mm# #^# ^# # #^ # 31 | # # # # # # ### # # ### # ### # 32 | # |^| |^| # # b# # 33 | # #-# #-# ###################^# 34 | # # 35 | ################################ 36 | 37 | 111111111111111111111111EEEEEEEE 38 | 133331333333111122222221E000000E 39 | 133331333333222122222221E000000E 40 | 133331333333122222122221E00EE0EE 41 | 113111111333122222122221E00E000E 42 | 133333331333111111122221E00E000E 43 | 1333A3A31333122222221111E00E000E 44 | 133333331333122222221EEEE0EEEEEE 45 | 1333A3A31333122222221700000E000E 46 | 1333333313331111111117000000000E 47 | 133333331333170777777700000E000E 48 | 1333A3A31333170000000700000EE0EE 49 | 1333333317077C7777700700000E000E 50 | 1333A3A31000000000C00700000E000E 51 | 13333333100000A0A0777777000E000E 52 | 133333331000000000007007000EEEEE 53 | 13333333100000A0A070700700077777 54 | 13333333100000000070700700000007 55 | 133333331000000000C0700700000007 56 | 11113111111000A0A070777777770007 57 | C0000000000000000070700000070007 58 | 70000000000000A0A070707777070007 59 | 700000000000000000C0700007070007 60 | 77C707C777C777C77770777707070007 61 | 70000000000700070000000707070007 62 | 70000000000700070777770707070007 63 | C0000000000C000C0C0C000707070007 64 | 70A0A00A0A0707770707077707077707 65 | 70000000000700000000000007000707 66 | 70A0A00A0A07C77C7777777777777707 67 | C0000000000000000000000000000007 68 | 777777777777C777C777C777C7777777 69 | -------------------------------------------------------------------------------- /src/main/resources/maps/level5.txt: -------------------------------------------------------------------------------- 1 | w=49 2 | h=25 3 | dir=345 4 | ################################################# 5 | #S | # mmhh# # ^ ^ # 6 | # |c A B ^ ^ C ^ X 7 | # | # # #^ ^ ^ # 8 | ##A## #-+---######################A#####-------## 9 | # ## | ################### ^ ^ # 10 | # ## | #h## ## ^## ## # 11 | # ## ^| ^## ^ ^ ^ #^## ## m## ## # 12 | # ##^ | ## # ^ # 13 | # ## | ## ################# # 14 | # ## | ^ ## ################# # 15 | # ## | ## ##h ^ ### # 16 | # ## | ## ^ ^ ^ ###A###A###A##h## # 17 | # ## | ## ## ^ #m m# #h## # 18 | # ## # ################*### #m #mbm#h## # 19 | # ## #####################A### ###### ## # 20 | # ## # # # # # ####m ## # 21 | # ## H A @ ## # 22 | # ########## # # # # ####mm## # 23 | # ############################################ # 24 | # ^ # ######################## # 25 | # ^ A ^ ^ A #m ^ # 26 | # #@## # ###### ##### # # # # #^# # 27 | # # m#################^^ h ^ # 28 | ###### ###### ######################### 29 | 30 | 111111111111111111111111EEE5EEEE5EEEE5EEEE5EEEEEE 31 | 14444444444414444444444100000D00000000E000000000E 32 | 144444444444444444444440000000000000000000000D00E 33 | 14444444444414444444444100000000000000E000000000E 34 | 114114144444111111111111EEE5EEEE5E0EE5EE0000000EE 35 | E88E144444441EEE5EEEE5EEEE5EEEE00000000000000000E 36 | E88E14444444444444444444444444E0E500E5D0E500E500E 37 | E88E144444441E4444444444444444E0EE00EE00EE00EE00E 38 | 5885144444441E4444444444444444E00000000000000000E 39 | E88E144444441544444444444444445EEEEEEEEEEEEEEE505 40 | E88E144444441E4444444444444444E666666666666666E0E 41 | E88E144444441E4444444444444444E622222222222266E0E 42 | E88E144444441E4444444444444444E662666266626626E0E 43 | 5885144444441E4444444444444444E622262226222626E0E 44 | E88E144414441EEE5EEEE5EEEE5EEEE622262226222626505 45 | E88E144444441111111111116666666662666266666626E0E 46 | E88E144444441444444444412226222622262226666226E0E 47 | E88E144444444444444444442222222222222222226226E0E 48 | 5885111111111444444444412226222622262226666226E0E 49 | E88EEE5EEEEEE111111111116666666666666666666666505 50 | E88888888888E44444444441EEEEEEEEEEEEEEEEEEEEEEE0E 51 | E888888888888444444444440000000000E0000000000000E 52 | E8888E5EE888E44444444441EEEEE0EEEEE0505050505050E 53 | 58888E88EEEEE11111111111E00000000000000000000000E 54 | EEEEEE8EEEEEE00000000000EEEE5E5EEEEEEEEEEEEEEEEEE 55 | -------------------------------------------------------------------------------- /src/main/resources/maps/level6.txt: -------------------------------------------------------------------------------- 1 | w=51 2 | h=24 3 | dir=190 4 | ################################################### 5 | # ^ | ^ # # |h# 6 | # ^ | # ^ # |m# 7 | # | ##### # ###### ^ # #m# 8 | # ##@#---# ### S# ^ # ##X ###^ ######## # # # 9 | # # # ## ## #^ ### ^ ## # # # #^# 10 | # # # ^ # #### ### ##### # # # # # # 11 | # # # # ######C###@###### ^# ##@## # # #^# 12 | # # ##### # ##b### ## ##### # # # # # # 13 | # # hm# # # # ## @ ## # #^^# # #^# 14 | # # hm# # B @ ######### ##^ # #mc# # # # 15 | # ######## # ##---# ####### ### # ^ #### # #^# 16 | # | # # m# ###mm## #### # # # # 17 | # | # A # ^##hh ## B # # |^# 18 | # | #### #m### ## ###^ # | # 19 | # | ####^ ###^####### ##### # |^# 20 | ##A#@##A#### ## m## ### # |^# 21 | ## # h# ### ^ ## m## ^ # # |^# 22 | ## # m# ##^ ^@ ## # |^# 23 | ##A####A###A###@###A##### # |^# 24 | # ^ ## ## # # # 25 | # ## ## # # 26 | # ^ ## H ## m@ ## 27 | ################################################### 28 | 29 | EEEEEEEEEEEEEE1111111111111100000000000000000000000 30 | E44444444444440000000014444400000000000000000000000 31 | E44444444444440000000014444400000000000000000000000 32 | E4444444444444EEEEE000144441EEEEE000000000000000000 33 | E49999444E44EEE000F000144441F000EEE0099999999000000 34 | E4944944444EE0000EE000144441EE0000EE092222229000000 35 | E4944944444E0000EE77001114111EE0000E092222229000000 36 | E4944944444E000EE7777071111111EE000E099999229000000 37 | E4944999994E00EE077C00071444111EE00E000209229000000 38 | E4944444494E00E00070000714441441E00E000209229000000 39 | E4944444494E000000B07C7711111441E00E000209229000000 40 | E4999999994E0EE00070221112244441EE0E000209999000000 41 | E4444444444E0E000070221442244410EE0E000200000000000 42 | E4444444444E00000070071444444100000E000200000000000 43 | E444444444EEEE0000707714444410000EEE000200000000000 44 | E44444444EE77000007770111111000000EEEEE000000000000 45 | EE4EEEE4EE7C0000000C700000100000000EEE0000000000000 46 | EE4E44E4E770000000007700010000000000E00000000000000 47 | 770744707700000000000700100000000000000000000000000 48 | 770777707770777777707777700000000000000000000000000 49 | 700000000000732223700000000000000000000000000000000 50 | 700000000000C32223700000000000000000000000000000000 51 | 700000000000732223700000000000000000000000000000000 52 | 77C7777C7777733333777777700000000000000000000000000 53 | -------------------------------------------------------------------------------- /src/main/resources/sound/bigfan.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/bigfan.wav -------------------------------------------------------------------------------- /src/main/resources/sound/door_unlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/door_unlock.wav -------------------------------------------------------------------------------- /src/main/resources/sound/doorclose.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/doorclose.wav -------------------------------------------------------------------------------- /src/main/resources/sound/doorwoosh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/doorwoosh.wav -------------------------------------------------------------------------------- /src/main/resources/sound/endlevel.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/endlevel.wav -------------------------------------------------------------------------------- /src/main/resources/sound/enemy_dead.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/enemy_dead.wav -------------------------------------------------------------------------------- /src/main/resources/sound/getammo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/getammo.wav -------------------------------------------------------------------------------- /src/main/resources/sound/laser0.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/laser0.wav -------------------------------------------------------------------------------- /src/main/resources/sound/laser1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/laser1.wav -------------------------------------------------------------------------------- /src/main/resources/sound/no_ammo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/no_ammo.wav -------------------------------------------------------------------------------- /src/main/resources/sound/nuclear_health.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/nuclear_health.wav -------------------------------------------------------------------------------- /src/main/resources/sound/player_dead.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/player_dead.wav -------------------------------------------------------------------------------- /src/main/resources/sound/player_hurt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/player_hurt.wav -------------------------------------------------------------------------------- /src/main/resources/sound/startlevel.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/startlevel.wav -------------------------------------------------------------------------------- /src/main/resources/sound/unlock.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/unlock.wav -------------------------------------------------------------------------------- /src/main/resources/sound/wallmove.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sound/wallmove.wav -------------------------------------------------------------------------------- /src/main/resources/sprites/ammo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/ammo.png -------------------------------------------------------------------------------- /src/main/resources/sprites/blast1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/blast1.png -------------------------------------------------------------------------------- /src/main/resources/sprites/blast2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/blast2.png -------------------------------------------------------------------------------- /src/main/resources/sprites/blast3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/blast3.png -------------------------------------------------------------------------------- /src/main/resources/sprites/key01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/key01.png -------------------------------------------------------------------------------- /src/main/resources/sprites/key02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/key02.png -------------------------------------------------------------------------------- /src/main/resources/sprites/key03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/key03.png -------------------------------------------------------------------------------- /src/main/resources/sprites/medkit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/medkit.png -------------------------------------------------------------------------------- /src/main/resources/sprites/nuclear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/sprites/nuclear.png -------------------------------------------------------------------------------- /src/main/resources/textures/door00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/door00.png -------------------------------------------------------------------------------- /src/main/resources/textures/door01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/door01.png -------------------------------------------------------------------------------- /src/main/resources/textures/door02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/door02.png -------------------------------------------------------------------------------- /src/main/resources/textures/door03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/door03.png -------------------------------------------------------------------------------- /src/main/resources/textures/exit00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/exit00.png -------------------------------------------------------------------------------- /src/main/resources/textures/exit01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/exit01.png -------------------------------------------------------------------------------- /src/main/resources/textures/generator00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/generator00.png -------------------------------------------------------------------------------- /src/main/resources/textures/generator01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/generator01.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall00.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall01.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall02.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall03.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall04.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall05.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall06.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall07.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall08.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall09.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall10.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall11.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall12.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall13.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall14.png -------------------------------------------------------------------------------- /src/main/resources/textures/wall15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/wall15.png -------------------------------------------------------------------------------- /src/main/resources/textures/window00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/textures/window00.png -------------------------------------------------------------------------------- /src/main/resources/ui/console_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/ui/console_font.png -------------------------------------------------------------------------------- /src/main/resources/ui/message_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/ui/message_font.png -------------------------------------------------------------------------------- /src/main/resources/ui/score_font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brackeen/Scared/3c698591e9c3ae9a0c973cf722e79ea3e899f4f4/src/main/resources/ui/score_font.png --------------------------------------------------------------------------------