diff --git a/Makefile.GnuLinux b/Makefile.GnuLinux new file mode 100644 index 0000000..fdf0a4b --- /dev/null +++ b/Makefile.GnuLinux @@ -0,0 +1,43 @@ +# +# Modification History +# +# 2006-June-27 Jason Rohrer +# Created. Condensed from X86 and PPC makefiles in Transcend project. +# +# 2007-April-23 Jason Rohrer +# Removed unneeded libraries. +# + + +## +# The common GnuLinux portion of Makefiles. +# Should not be made manually---used by configure to build Makefiles. +## + + +PLATFORM_COMPILE_FLAGS = -DLINUX + + +# various GL and X windows librariesneeded for linux +# also need portaudio library (which in turn needs pthreads) +PLATFORM_LINK_FLAGS = -L/usr/X11R6/lib -lGL -lGLU -lX11 -lSDL -lpthread -lpng -lz + + +# All platforms but OSX support g++ and need no linker hacks +GXX = g++ +LINK_FLAGS = + + + +## +# Platform-specific minorGems file path prefixes +## + +PLATFORM = Linux +PLATFORM_PATH = linux + +TIME_PLATFORM = Unix +TIME_PLATFORM_PATH = unix + +DIRECTORY_PLATFORM = Unix +DIRECTORY_PLATFORM_PATH = unix diff --git a/Makefile.MacOSX b/Makefile.MacOSX new file mode 100644 index 0000000..e269e21 --- /dev/null +++ b/Makefile.MacOSX @@ -0,0 +1,44 @@ +# +# Modification History +# +# 2006-June-27 Jason Rohrer +# Created. Adapted from Transcend project. +# + + +## +# The common MacOSX portion of Makefiles. +# Should not be made manually---used by configure to build Makefiles. +## + + +# __mac__ to trigger certain mac-specific coping code +# paths to GL and GLUT headers + +PLATFORM_COMPILE_FLAGS = -DBSD -D__mac__ -I/System/Library/Frameworks/OpenGL.framework/Headers + + +# various frameworks to support OpenGL and SDL +# static linking against zlib and libpng +PLATFORM_LINK_FLAGS = -framework OpenGL -framework SDL -framework Cocoa mac/SDLMain.m /usr/lib/libz.a /usr/lib/libpng.a + + +# Nothing special for OS X here +GXX = g++ +LINK_FLAGS = + + + +## +# Platform-specific minorGems file path prefixes +## + +PLATFORM = Linux +PLATFORM_PATH = linux + +TIME_PLATFORM = Unix +TIME_PLATFORM_PATH = unix + +DIRECTORY_PLATFORM = Unix +DIRECTORY_PLATFORM_PATH = unix + diff --git a/Makefile.MinGW b/Makefile.MinGW new file mode 100644 index 0000000..aaf7a1f --- /dev/null +++ b/Makefile.MinGW @@ -0,0 +1,58 @@ +# +# Modification History +# +# 2003-November-2 Jason Rohrer +# Created. +# +# 2003-November-10 Jason Rohrer +# Removed pthread flag. +# Changed LINUX flag to WIN_32 flag. +# Added wsock32 library flag. +# + + +## +# The common MinGW (GNU for Win32) portion of Makefiles. +# Should not be made manually---used by configure to build Makefiles. +## + + +# static lib flag needed to link compiled objs against miniupnp +PLATFORM_COMPILE_FLAGS = -DWIN_32 -DSTATICLIB + + +# need various GL libraries, winmm, and portaudio +# -mwindows tells mingw to hide the dos command window on launch +PLATFORM_LINK_FLAGS = -lopengl32 -lglu32 -lmingw32 -lSDLmain -lSDL -mwindows -lwsock32 -lpng -lz + + +# All platforms but OSX support g++ and need no linker hacks +GXX = g++ +LINK_FLAGS = + + +# don't build icon.o when make invoked with no arguments! +all: SleepIsDeath + +icon.o: ../build/win32/icon.ico ../build/win32/icon.rc + cp ../build/win32/icon.ico ../build/win32/icon.rc . + windres -i icon.rc -o icon.o + +ICON_FILE = icon.o + + + +## +# Platform-specific minorGems file path prefixes +## + +PLATFORM = Win32 +PLATFORM_PATH = win32 + +TIME_PLATFORM = Win32 +TIME_PLATFORM_PATH = win32 + +DIRECTORY_PLATFORM = Win32 +DIRECTORY_PLATFORM_PATH = win32 + + diff --git a/Makefile.common b/Makefile.common new file mode 100644 index 0000000..b72836f --- /dev/null +++ b/Makefile.common @@ -0,0 +1,82 @@ +# +# Modification History +# +# 2004-April-30 Jason Rohrer +# Created. Modified from MUTE source. +# +# 2005-August-29 Jason Rohrer +# Added optimization options. +# +# 2007-April-23 Jason Rohrer +# Upgraded to latest minorGems dependency format. +# + + +## +# The common portion of all Makefiles. +# Should not be made manually---used by configure to build Makefiles. +## + + + +EXE_LINKER = ${GXX} + +RANLIB = ranlib +LIBRARY_LINKER = ar + + +DEBUG_ON_FLAG = -g #-DDEBUG_MEMORY +DEBUG_OFF_FLAG = + +DEBUG_FLAG = ${DEBUG_ON_FLAG} + + +PROFILE_ON_FLAG = -pg -DUSE_GPROF_THREADS +PROFILE_OFF_FLAG = + +PROFILE_FLAG = ${PROFILE_OFF_FLAG} + + +OPTIMIZE_ON_FLAG = -O9 +OPTIMIZE_OFF_FLAG = -O0 + +OPTIMIZE_FLAG = ${OPTIMIZE_OFF_FLAG} + + + +# common to all platforms +SOCKET_UDP_PLATFORM_PATH = unix +SOCKET_UDP_PLATFORM = Unix + + + +COMPILE_FLAGS = -Wall -Wwrite-strings -Wchar-subscripts -Wparentheses ${DEBUG_FLAG} ${PLATFORM_COMPILE_FLAGS} ${PROFILE_FLAG} ${OPTIMIZE_FLAG} -I${ROOT_PATH} + +COMMON_LIBS = ${ROOT_PATH}/minorGems/network/upnp/miniupnpc/libminiupnpc.a + + + + +COMPILE = ${GXX} ${COMPILE_FLAGS} -c +EXE_LINK = ${EXE_LINKER} ${COMPILE_FLAGS} ${LINK_FLAGS} +LIBRARY_LINK = ${LIBRARY_LINKER} cru + + +# +# Generic: +# +# Map all .cpp C++ and C files into .o object files +# +# $@ represents the name.o file +# $< represents the name.cpp file +# +.cpp.o: + ${COMPILE} -o $@ $< +.c.o: + ${COMPILE} -o $@ $< + + + + + + diff --git a/README.md b/README.md deleted file mode 100644 index 82fc270..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# SleepIsDeath -a storytelling game for two players diff --git a/build/icon_base.png b/build/icon_base.png new file mode 100644 index 0000000..a1bd4c6 Binary files /dev/null and b/build/icon_base.png differ diff --git a/build/macOSX/SleepIsDeath.app/Contents/Info.plist b/build/macOSX/SleepIsDeath.app/Contents/Info.plist new file mode 100644 index 0000000..b0cf8db --- /dev/null +++ b/build/macOSX/SleepIsDeath.app/Contents/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleIdentifier + org.passage + CFBundleDevelopmentRegion + English + CFBundleExecutable + SleepIsDeath + CFBundleIconFile + SleepIsDeath.icns + CFBundleName + SleepIsDeath + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 0 + CFBundleShortVersionString + 0 + CFBundleGetInfoString + SleepIsDeath + CFBundleLongVersionString + 0 + NSHumanReadableCopyright + Public Domain, 2010 + LSRequiresCarbon + + CSResourcesFileMapped + + + diff --git a/build/macOSX/SleepIsDeath.app/Contents/PkgInfo b/build/macOSX/SleepIsDeath.app/Contents/PkgInfo new file mode 100644 index 0000000..bd04210 --- /dev/null +++ b/build/macOSX/SleepIsDeath.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.icns b/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.icns new file mode 100644 index 0000000..efc4e87 Binary files /dev/null and b/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.icns differ diff --git a/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.rsrc b/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.rsrc new file mode 100644 index 0000000..ba75391 Binary files /dev/null and b/build/macOSX/SleepIsDeath.app/Contents/Resources/SleepIsDeath.rsrc differ diff --git a/build/macOSX/dmgNotes.txt b/build/macOSX/dmgNotes.txt new file mode 100644 index 0000000..4fddc5d --- /dev/null +++ b/build/macOSX/dmgNotes.txt @@ -0,0 +1,23 @@ +To mount dmg: + +hdiutil mount Test.dmg + + +To unmount: + +hdiutil unmount /Volumes/Test + + +To convert DMG to compressed: + +hdiutil convert Test.dmg -format UDZO -o Test_compressed.dmg + + +To convert compressed DMG to R/W: + +hdiutil convert Test_compressed.dmg -format UDRW -o Test.dmg + + +To rename a volume: + +diskutil rename /Volumes/NameA NameB diff --git a/build/macOSX/iconColor.png b/build/macOSX/iconColor.png new file mode 100644 index 0000000..eccb82c Binary files /dev/null and b/build/macOSX/iconColor.png differ diff --git a/build/macOSX/iconMask.png b/build/macOSX/iconMask.png new file mode 100644 index 0000000..4d77896 Binary files /dev/null and b/build/macOSX/iconMask.png differ diff --git a/build/makeDistributionMacOSX b/build/makeDistributionMacOSX new file mode 100755 index 0000000..5382362 --- /dev/null +++ b/build/makeDistributionMacOSX @@ -0,0 +1,72 @@ +#!/bin/sh + +# +# Modification History +# +# 2007-November-12 Jason Rohrer +# Copied from Cultivation build. +# + + +if [ $# -lt 3 ] ; then + echo "Usage: $0 release_name unix_platform_name path_to_SDL.framework" + exit 1 +fi + + +rm -rf mac + +mkdir mac + + + +mkdir mac/SleepIsDeath +mkdir mac/SleepIsDeath/graphics +mkdir mac/SleepIsDeath/settings +mkdir mac/SleepIsDeath/languages +mkdir mac/SleepIsDeath/templates +mkdir mac/SleepIsDeath/loadingBay +mkdir mac/SleepIsDeath/importOldCache + + +cp ../gameSource/resourceSet15.tar.gz . +tar xzf resourceSet15.tar.gz +cp -r resourceSet15/* mac/SleepIsDeath/ +rm -r resourceSet15/ resourceSet15.tar.gz + + + +cp ../gameSource/graphics/*.tga mac/SleepIsDeath/graphics +cp ../gameSource/settings/*.ini mac/SleepIsDeath/settings +cp ../gameSource/languages/*.txt mac/SleepIsDeath/languages +cp ../gameSource/templates/*.php mac/SleepIsDeath/templates +cp ../gameSource/templates/*.html mac/SleepIsDeath/templates +cp ../gameSource/templates/*.png mac/SleepIsDeath/templates +cp ../gameSource/language.txt mac/SleepIsDeath +cp ../documentation/Readme.txt mac/SleepIsDeath +cp ../documentation/Upgrading.txt mac/SleepIsDeath + + + + +cp -r macOSX/SleepIsDeath.app mac/SleepIsDeath/SleepIsDeath.app +cp ../gameSource/SleepIsDeath mac/SleepIsDeath/SleepIsDeath.app/Contents/MacOS + +rm -r mac/SleepIsDeath/SleepIsDeath.app/CVS +rm -r mac/SleepIsDeath/SleepIsDeath.app/Contents/CVS +rm -r mac/SleepIsDeath/SleepIsDeath.app/Contents/MacOS/CVS +rm -r mac/SleepIsDeath/SleepIsDeath.app/Contents/Resources/CVS +rm -r mac/SleepIsDeath/SleepIsDeath.app/Contents/Frameworks/CVS + +# install SDL framework +cp -r $3 mac/SleepIsDeath/SleepIsDeath.app/Contents/Frameworks/ + +cd mac +tar cf "SleepIsDeath_$1_$2.tar" SleepIsDeath +gzip "SleepIsDeath_$1_$2.tar" + + + + + + diff --git a/build/makeDistributions b/build/makeDistributions new file mode 100755 index 0000000..2874a47 --- /dev/null +++ b/build/makeDistributions @@ -0,0 +1,102 @@ +#!/bin/sh + +# +# Modification History +# +# 2007-November-12 Jason Rohrer +# Copied from Cultivation build. +# + + +if [ $# -lt 2 ] ; then + echo "Usage: $0 release_name unix_platform_name" + exit 1 +fi + + +rm -rf unix +rm -rf windows + +mkdir windows +mkdir unix + + +# work on unix tree first +mkdir unix/SleepIsDeath +mkdir unix/SleepIsDeath/graphics +mkdir unix/SleepIsDeath/settings +mkdir unix/SleepIsDeath/languages +mkdir unix/SleepIsDeath/templates +mkdir unix/SleepIsDeath/loadingBay +mkdir unix/SleepIsDeath/importOldCache + + + +cp ../gameSource/resourceSet15.tar.gz . +tar xzf resourceSet15.tar.gz +cp -r resourceSet15/* unix/SleepIsDeath/ +rm -r resourceSet15/ resourceSet15.tar.gz + + + +cp ../gameSource/graphics/*.tga unix/SleepIsDeath/graphics +cp ../gameSource/settings/*.ini unix/SleepIsDeath/settings +cp ../gameSource/languages/*.txt unix/SleepIsDeath/languages +cp ../gameSource/templates/*.php unix/SleepIsDeath/templates +cp ../gameSource/templates/*.html unix/SleepIsDeath/templates +cp ../gameSource/templates/*.png unix/SleepIsDeath/templates +cp ../gameSource/language.txt unix/SleepIsDeath +cp ../documentation/Readme.txt unix/SleepIsDeath +cp ../documentation/Upgrading.txt unix/SleepIsDeath + + + +# duplicate unix tree so far to make windows tree +cp -r unix/SleepIsDeath windows/ + +cp ../gameSource/SleepIsDeath unix/SleepIsDeath/ + +cp win32/SleepIsDeath.exe win32/*.dll windows/SleepIsDeath/ + +cd unix +tar cf "SleepIsDeath_$1_$2.tar" SleepIsDeath +gzip "SleepIsDeath_$1_$2.tar" + + +cd .. + +g++ -o unix2dos unix2dos.c +cp unix2dos windows +cp unix2dosScript windows + + +cd windows + + +for file in SleepIsDeath/languages/*.txt +do + ./unix2dosScript "$file" +done + +for file in SleepIsDeath/templates/*.php +do + ./unix2dosScript "$file" +done + +for file in SleepIsDeath/templates/*.html +do + ./unix2dosScript "$file" +done + + +./unix2dosScript "SleepIsDeath/resourceCache/stringDatabase.txt" +./unix2dosScript "SleepIsDeath/Readme.txt" +./unix2dosScript "SleepIsDeath/Upgrading.txt" + + +zip -r "SleepIsDeath_$1_Windows.zip" SleepIsDeath + + + + + diff --git a/build/source/cleanSrc b/build/source/cleanSrc new file mode 100755 index 0000000..9f1fe17 --- /dev/null +++ b/build/source/cleanSrc @@ -0,0 +1,18 @@ +rm -r minorGems/ai +rm -r minorGems/bench +rm -r minorGems/doc +rm -r minorGems/examples +rm -r minorGems/sound + +rm -r SleepIsDeath/documentation/guides +rm -r SleepIsDeath/documentation/html +rm -r SleepIsDeath/documentation/installation +rm -r SleepIsDeath/documentation/press +rm -r SleepIsDeath/documentation/releasePlan +rm -r SleepIsDeath/documentation/teaser + +rm SleepIsDeath/gameSource/resourcePack*.tar.gz +rm SleepIsDeath/gameSource/resourceSet10.tar.gz +rm SleepIsDeath/gameSource/resourceSet12.tar.gz +rm SleepIsDeath/gameSource/resourceSet14.tar.gz + diff --git a/build/source/exportSrc b/build/source/exportSrc new file mode 100755 index 0000000..701f563 --- /dev/null +++ b/build/source/exportSrc @@ -0,0 +1,9 @@ +SOURCEDIR=`pwd` +echo "running export from $SOURCEDIR" + +cd ../../../../minorGems +hg archive $SOURCEDIR/minorGems + +cd $SOURCEDIR + +cvs -z3 -d:ext:jcr13@hcsoftware.cvs.sourceforge.net:/cvsroot/hcsoftware export -r HEAD SleepIsDeath \ No newline at end of file diff --git a/build/source/runToBuild b/build/source/runToBuild new file mode 100755 index 0000000..c9e1af5 --- /dev/null +++ b/build/source/runToBuild @@ -0,0 +1,82 @@ +#!/bin/bash + +# +# Modification History +# +# 2010-April-2 Andy Sommerville +# - Added a few "|| exit 1" to terminate as soon as error occurs. +# - Read menu choice from argv[1]. +# - Added "-p" to mkdir to suppress "already exists" message. +# - Added automatic backup of "resourceCache". (wish I'd thought of that a few minutes ago....) +# +# 2007-November-12 Jason Rohrer +# Copied from Cultivation. +# + + +cd SleepIsDeath +chmod u+x ./configure +./configure $@ || exit 1 + + +echo "Building miniUPNP..." + +cd ../minorGems/network/upnp/miniupnpc +make || exit 1 +cd ../../../../SleepIsDeath + + +cd gameSource + + +echo "Building SleepIsDeath..." + +make || exit 1 + + + +cd ../.. + +mkdir -p graphics +mkdir -p settings +mkdir -p languages +mkdir -p templates +mkdir -p loadingBay +mkdir -p importOldCache + +# this now happens from resource pack expansion, below +#mkdir resourceCache +#cd resourceCache +#mkdir object room sprite tile +#cd .. + + +cp SleepIsDeath/gameSource/SleepIsDeath ./SleepIsDeathApp +cp SleepIsDeath/documentation/Readme.txt . +cp SleepIsDeath/documentation/Upgrading.txt . +cp SleepIsDeath/gameSource/graphics/* ./graphics +cp SleepIsDeath/gameSource/settings/* ./settings +cp SleepIsDeath/gameSource/languages/* ./languages +cp SleepIsDeath/gameSource/language.txt ./ +cp SleepIsDeath/gameSource/templates/*.php ./templates +cp SleepIsDeath/gameSource/templates/*.html ./templates +cp SleepIsDeath/gameSource/templates/*.png ./templates + +# backup old resources +if [ -d resourceCache ] ; then + BACKUP=resourceCache-backup-$(date '+%Y%m%d%H%M%S').tar.gz + echo "Backing up resources to $BACKUP" + tar -czf $BACKUP resourceCache +fi + +# copy default resources +tar xzf SleepIsDeath/gameSource/resourceSet15.tar.gz + +cp -r resourceSet15/* . + + +echo "Run SleepIsDeathApp to play." + + + + diff --git a/build/unix2dos.c b/build/unix2dos.c new file mode 100644 index 0000000..9ff8902 --- /dev/null +++ b/build/unix2dos.c @@ -0,0 +1,22 @@ +#include +#include + +int +main(void) +{ + while(1) { + int c = getchar(); + + if(c == EOF) + exit(0); + + if(c == '\n') { + putchar(015); /* ^M */ + putchar(012); /* ^J */ + } else { + putchar(c); + } + } + + exit(0); +} diff --git a/build/unix2dosScript b/build/unix2dosScript new file mode 100755 index 0000000..bfe4321 --- /dev/null +++ b/build/unix2dosScript @@ -0,0 +1,7 @@ + + +cat "$1" | ./unix2dos > tempUNIX2DOS.txt + +cp -f tempUNIX2DOS.txt "$1" + +rm -f tempUNIX2DOS.txt \ No newline at end of file diff --git a/build/win32/SDL.dll b/build/win32/SDL.dll new file mode 100755 index 0000000..3ce97a5 Binary files /dev/null and b/build/win32/SDL.dll differ diff --git a/build/win32/icon.ico b/build/win32/icon.ico new file mode 100644 index 0000000..d100bef Binary files /dev/null and b/build/win32/icon.ico differ diff --git a/build/win32/icon.png b/build/win32/icon.png new file mode 100644 index 0000000..7eb4f8a Binary files /dev/null and b/build/win32/icon.png differ diff --git a/build/win32/icon.rc b/build/win32/icon.rc new file mode 100644 index 0000000..86e8e9b --- /dev/null +++ b/build/win32/icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icon.ico" diff --git a/build/win32/icon_installer.ico b/build/win32/icon_installer.ico new file mode 100644 index 0000000..6e63d6f Binary files /dev/null and b/build/win32/icon_installer.ico differ diff --git a/build/win32/icon_installer.png b/build/win32/icon_installer.png new file mode 100644 index 0000000..645d4dc Binary files /dev/null and b/build/win32/icon_installer.png differ diff --git a/build/win32/libpng3.dll b/build/win32/libpng3.dll new file mode 100755 index 0000000..649ec64 Binary files /dev/null and b/build/win32/libpng3.dll differ diff --git a/build/win32/zlib1.dll b/build/win32/zlib1.dll new file mode 100755 index 0000000..076f503 Binary files /dev/null and b/build/win32/zlib1.dll differ diff --git a/configure b/configure new file mode 100755 index 0000000..4b32ce9 --- /dev/null +++ b/configure @@ -0,0 +1,104 @@ +#!/bin/bash + +# +# Modification History +# +# 2010-April-2 Andy Sommerville +# Allow menu choice from argv. +# "exit 1" when user chooses 'q'; terminate build. +# +# 2006-June-27 Jason Rohrer +# Copied/modified from Transcend project. Dropped a platforms that were only +# nominally supported. +# + +if [[ "$1" < "4" ]] ; then + if [[ "$1" > "0" ]] ; then + platformSelection="$1" + fi +fi +while [ -z "$platformSelection" ] +do + + echo "select platform:" + + echo " 1 -- GNU/Linux" + echo " 2 -- MacOSX" + echo " 3 -- Win32 using MinGW" + echo " q -- quit" + + echo "" + echo -n "> " + + read platformSelection + + + if [ "$platformSelection" = "q" ] + then + exit 1 + fi + + + # use ASCII comparison. + + if [[ "$platformSelection" > "3" ]] + then + platformSelection="" + fi + + if [[ "$platformSelection" < "1" ]] + then + platformSelection="" + fi + +done + + +# use partial makefiles from minorGems project +makefileMinorGems="../minorGems/build/Makefile.minorGems" +makefileMinorGemsTargets="../minorGems/build/Makefile.minorGems_targets" + + + +platformName="Generic" +platformMakefile="generic" + + + +case "$platformSelection" in + + + "1" ) + platformName="GNU/Linux" + platformMakefile="Makefile.GnuLinux" + ;; + + + "2" ) + platformName="MacOSX" + platformMakefile="Makefile.MacOSX" + ;; + + + "3" ) + platformName="Win32 MinGW" + platformMakefile="Makefile.MinGW" + ;; + + +esac + + + +rm -f Makefile.temp +echo "# Auto-generated by game7/configure for the $platformName platform. Do not edit manually." > Makefile.temp + +rm -f gameSource/Makefile +cat Makefile.temp $platformMakefile Makefile.common $makefileMinorGems gameSource/Makefile.all $makefileMinorGemsTargets > gameSource/Makefile + + +rm Makefile.temp + + + +exit diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..5fda3b0 --- /dev/null +++ b/demo/index.html @@ -0,0 +1,5 @@ +
+ + + +
\ No newline at end of file diff --git a/demo/server.php b/demo/server.php new file mode 100644 index 0000000..7136592 --- /dev/null +++ b/demo/server.php @@ -0,0 +1,914 @@ + +Demo Permissions Server Web-based setup + + +
+ +
+ +
"; + +$setup_footer = " +
+
+
+"; + + + + + + +// ensure that magic quotes are on (adding slashes before quotes +// so that user-submitted data can be safely submitted in DB queries) +if( !get_magic_quotes_gpc() ) { + // force magic quotes to be added + $_GET = array_map( 'dps_addslashes_deep', $_GET ); + $_POST = array_map( 'dps_addslashes_deep', $_POST ); + $_REQUEST = array_map( 'dps_addslashes_deep', $_REQUEST ); + $_COOKIE = array_map( 'dps_addslashes_deep', $_COOKIE ); + } + + + + + + +// all calls need to connect to DB, so do it once here +dps_connectToDatabase(); + +// close connection down below (before function declarations) + + +// testing: +//sleep( 5 ); + + +// general processing whenver server.php is accessed directly + + + + +// grab POST/GET variables +$action = ""; +if( isset( $_REQUEST[ "action" ] ) ) { + $action = $_REQUEST[ "action" ]; + } + +$debug = ""; +if( isset( $_REQUEST[ "debug" ] ) ) { + $debug = $_REQUEST[ "debug" ]; + } + +$remoteIP = ""; +if( isset( $_SERVER[ "REMOTE_ADDR" ] ) ) { + $remoteIP = $_SERVER[ "REMOTE_ADDR" ]; + } + + + + +if( $action == "version" ) { + global $dps_version; + echo "$dps_version"; + } +else if( $action == "show_log" ) { + dps_showLog(); + } +else if( $action == "clear_log" ) { + dps_clearLog(); + } +else if( $action == "create_demo_id" ) { + dps_createDemoID(); + } +else if( $action == "block_demo_id" ) { + dps_blockDemoID(); + } +else if( $action == "delete_demo_id" ) { + dps_deleteDemoID(); + } +else if( $action == "check_permitted" ) { + dps_checkPermitted(); + } +else if( $action == "show_data" ) { + dps_showData(); + } +else if( $action == "show_detail" ) { + dps_showDetail(); + } +else if( $action == "dps_setup" ) { + global $setup_header, $setup_footer; + echo $setup_header; + + echo "

Demo Permissions Server Web-based Setup

"; + + echo "Creating tables:
"; + + echo "
+
+ +
"; + + dps_setupDatabase(); + + echo "


"; + + echo $setup_footer; + } +else if( preg_match( "/server\.php/", $_SERVER[ "SCRIPT_NAME" ] ) ) { + // server.php has been called without an action parameter + + // the preg_match ensures that server.php was called directly and + // not just included by another script + + // quick (and incomplete) test to see if we should show instructions + global $tableNamePrefix; + + // check if our "games" table exists + $tableName = $tableNamePrefix . "demos"; + + $exists = dps_doesTableExist( $tableName ); + + if( $exists ) { + echo "Demo Permissions server database setup and ready"; + } + else { + // start the setup procedure + + global $setup_header, $setup_footer; + echo $setup_header; + + echo "

Demo Permissions Server Web-based Setup

"; + + echo "Demo Permissions Server will walk you through a " . + "brief setup process.

"; + + echo "Step 1: ". + "". + "create the database tables"; + + echo $setup_footer; + } + } + + + +// done processing +// only function declarations below + +dps_closeDatabase(); + + + + + + + +/** + * Creates the database tables needed by seedBlogs. + */ +function dps_setupDatabase() { + global $tableNamePrefix; + + $tableName = $tableNamePrefix . "log"; + if( ! dps_doesTableExist( $tableName ) ) { + + // this table contains general info about the server + // use INNODB engine so table can be locked + $query = + "CREATE TABLE $tableName(" . + "entry TEXT NOT NULL, ". + "entry_time DATETIME NOT NULL );"; + + $result = dps_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + + + + $tableName = $tableNamePrefix . "demos"; + if( ! dps_doesTableExist( $tableName ) ) { + + // this table contains general info about each game + // use INNODB engine so table can be locked + $query = + "CREATE TABLE $tableName(" . + "demo_id CHAR(10) NOT NULL PRIMARY KEY," . + "creation_date DATETIME NOT NULL," . + "last_run_date DATETIME NOT NULL," . + "note CHAR(40) NOT NULL," . + "blocked TINYINT NOT NULL," . + "run_count INT NOT NULL );"; + + $result = dps_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + + + + + $tableName = $tableNamePrefix . "runs"; + if( ! dps_doesTableExist( $tableName ) ) { + + // this table contains information for each user + $query = + "CREATE TABLE $tableName(" . + "demo_id CHAR(10) NOT NULL," . + "run_date DATETIME NOT NULL," . + "blocked TINYINT NOT NULL," . + "ip_address CHAR(255) NOT NULL," . + "PRIMARY KEY( demo_id, run_date ) );"; + + $result = dps_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + } + + + +function dps_showLog() { + $password = dps_checkPassword( "show_log" ); + + echo "[Main]


"; + + global $tableNamePrefix; + + $query = "SELECT * FROM $tableNamePrefix"."log ". + "ORDER BY entry_time DESC;"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + + + echo "". + "Clear log"; + + echo "
"; + + echo "$numRows log entries:


\n"; + + + for( $i=0; $i<$numRows; $i++ ) { + $time = mysql_result( $result, $i, "entry_time" ); + $entry = mysql_result( $result, $i, "entry" ); + + echo "$time:
$entry
\n"; + } + } + + + +function dps_clearLog() { + $password = dps_checkPassword( "clear_log" ); + + echo "[Main]
"; + + global $tableNamePrefix; + + $query = "DELETE FROM $tableNamePrefix"."log;"; + $result = dps_queryDatabase( $query ); + + if( $result ) { + echo "Log cleared."; + } + else { + echo "DELETE operation failed?"; + } + } + + + + + + + +function dps_createDemoID() { + $password = dps_checkPassword( "create_demo_id" ); + + global $tableNamePrefix; + + + $note = ""; + if( isset( $_REQUEST[ "note" ] ) ) { + $note = $_REQUEST[ "note" ]; + } + + + + $found_unused_id = 0; + $salt = 0; + + + while( ! $found_unused_id ) { + + $randVal = rand(); + + $hash = md5( $note . uniqid( "$randVal"."$salt", true ) ); + + $hash = strtoupper( $hash ); + + + $demo_id = substr( $hash, 0, 10 ); + + + + // make code more human-friendly (alpha only) + + $digitArray = + array( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ); + $letterArray = + array( "W", "H", "J", "K", "X", "M", "N", "P", "T", "Y" ); + + $demo_id = str_replace( $digitArray, $letterArray, $demo_id ); + + + + $query = "INSERT INTO $tableNamePrefix". "demos VALUES ( " . + "'$demo_id', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, ". + "'$note', '0', '0' );"; + + + $result = mysql_query( $query ); + + if( $result ) { + $found_unused_id = 1; + + global $remoteIP; + dps_log( "Demo $demo_id created by $remoteIP" ); + + + //echo "$demo_id"; + dps_showData(); + } + else { + global $debug; + if( $debug == 1 ) { + echo "Duplicate ids? Error: " . mysql_error() ."
"; + } + // try again + $salt += 1; + } + } + + } + + + +function dps_blockDemoID() { + $password = dps_checkPassword( "block_demo_id" ); + + + global $tableNamePrefix; + + $demo_id = ""; + if( isset( $_REQUEST[ "demo_id" ] ) ) { + $demo_id = $_REQUEST[ "demo_id" ]; + } + + $demo_id = strtoupper( $demo_id ); + + + $blocked = ""; + if( isset( $_REQUEST[ "blocked" ] ) ) { + $blocked = $_REQUEST[ "blocked" ]; + } + + + global $remoteIP; + + + + + $query = "SELECT * FROM $tableNamePrefix"."demos ". + "WHERE demo_id = '$demo_id';"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + if( $numRows == 1 ) { + + + $query = "UPDATE $tableNamePrefix"."demos SET " . + "blocked = '$blocked' " . + "WHERE demo_id = '$demo_id';"; + + $result = dps_queryDatabase( $query ); + + + dps_log( "$demo_id block changed to $blocked by $remoteIP" ); + + dps_showData(); + } + else { + dps_log( "$demo_id not found for $remoteIP" ); + + echo "$demo_id not found"; + } + } + + + +function dps_deleteDemoID() { + $password = dps_checkPassword( "delete_demo_id" ); + + global $tableNamePrefix, $remoteIP; + + $demo_id = ""; + if( isset( $_REQUEST[ "demo_id" ] ) ) { + $demo_id = $_REQUEST[ "demo_id" ]; + } + + $demo_id = strtoupper( $demo_id ); + + + $query = "DELETE FROM $tableNamePrefix"."demos ". + "WHERE demo_id = '$demo_id';"; + $result = dps_queryDatabase( $query ); + + if( $result ) { + dps_log( "$demo_id deleted by $remoteIP" ); + + echo "$demo_id deleted.
"; + + dps_showData(); + } + else { + dps_log( "$demo_id delete failed for $remoteIP" ); + + echo "DELETE operation failed?"; + } + } + + + +function dps_checkPermitted() { + $demo_id = ""; + if( isset( $_REQUEST[ "demo_id" ] ) ) { + $demo_id = $_REQUEST[ "demo_id" ]; + } + + $demo_id = strtoupper( $demo_id ); + + + $challenge = ""; + if( isset( $_REQUEST[ "challenge" ] ) ) { + $challenge = $_REQUEST[ "challenge" ]; + } + + + global $tableNamePrefix, $remoteIP; + + + + + $query = "SELECT * FROM $tableNamePrefix"."demos ". + "WHERE demo_id = '$demo_id';"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + if( $numRows == 1 ) { + + $row = mysql_fetch_array( $result, MYSQL_ASSOC ); + + $blocked = $row[ "blocked" ]; + + + // catalog blocked runs, too + $run_count = $row[ "run_count" ]; + + $run_count ++; + + + $query = "UPDATE $tableNamePrefix"."demos SET " . + "last_run_date = CURRENT_TIMESTAMP, " . + "run_count = '$run_count' " . + "WHERE demo_id = '$demo_id';"; + + + $result = dps_queryDatabase( $query ); + + $query = "INSERT INTO $tableNamePrefix". "runs VALUES ( " . + "'$demo_id', CURRENT_TIMESTAMP, '$blocked', '$remoteIP' );"; + + $result = mysql_query( $query ); + + if( !$blocked ) { + + dps_log( "$demo_id permitted to run by $remoteIP" ); + + + // response to challenge using shared secret + + global $sharedSecret; + + $hash = sha1( $challenge . $sharedSecret ); + + $hash = strtoupper( $hash ); + + + + echo "permitted $hash"; + return; + } + } + + dps_log( "$demo_id denied to run by $remoteIP" ); + + echo "denied"; + } + + + + + +function dps_showData() { + $password = dps_checkPassword( "show_data" ); + + global $tableNamePrefix, $remoteIP; + + + echo "[Main]"; + + + $query = "SELECT * FROM $tableNamePrefix"."demos ". + "ORDER BY last_run_date DESC;"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + // form + ?> + +
+ Create new ID:
+
+ + + Note: + + +
+
+ +

\n"; + + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo ""; + echo "\n"; + + + for( $i=0; $i<$numRows; $i++ ) { + $demo_id = mysql_result( $result, $i, "demo_id" ); + $creation = mysql_result( $result, $i, "creation_date" ); + $lastRun = mysql_result( $result, $i, "last_run_date" ); + $count = mysql_result( $result, $i, "run_count" ); + $note = mysql_result( $result, $i, "note" ); + $blocked = mysql_result( $result, $i, "blocked" ); + + $block_toggle = ""; + + if( $blocked ) { + $blocked = "BLOCKED"; + $block_toggle = "unblock"; + + } + else { + $blocked = ""; + $block_toggle = "block"; + + } + + + // challenge to include in test link + $randVal = rand(); + + $challenge = md5( $demo_id . uniqid( "$randVal", true ) ); + + + echo "\n"; + + echo "\n"; + echo "\n"; + echo "\n"; + echo " "; + echo ""; + echo ""; + echo ""; + + echo "\n"; + } + echo "
Demo IDNoteBlocked?Created Test Last RunRun Count
$demo_id$note$blocked [$block_toggle]$creation[run test]$lastRun$count runs "; + + echo "[list]
"; + echo "
"; + + echo "". + "Show log"; + echo "
"; + echo "Generated for $remoteIP\n"; + + } + + + +function dps_showDetail() { + $password = dps_checkPassword( "show_detail" ); + + echo "[Main]"; + + global $tableNamePrefix; + + + $demo_id = ""; + if( isset( $_REQUEST[ "demo_id" ] ) ) { + $demo_id = $_REQUEST[ "demo_id" ]; + } + + $demo_id = strtoupper( $demo_id ); + + + $query = "SELECT * FROM $tableNamePrefix"."runs ". + "WHERE demo_id = '$demo_id' ORDER BY run_date DESC;"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + echo "$numRows runs for $demo_id:"; + + echo " [DELETE this id]"; + + echo "


\n"; + + + for( $i=0; $i<$numRows; $i++ ) { + $date = mysql_result( $result, $i, "run_date" ); + $ipAddress = mysql_result( $result, $i, "ip_address" ); + + $blocked = mysql_result( $result, $i, "blocked" ); + + if( $blocked ) { + $blocked = "BLOCKED"; + } + else { + $blocked = ""; + } + + echo "$date: $ipAddress $blocked
\n"; + } + } + + + + + + +// general-purpose functions down here, many copied from seedBlogs + +/** + * Connects to the database according to the database variables. + */ +function dps_connectToDatabase() { + global $databaseServer, + $databaseUsername, $databasePassword, $databaseName; + + + mysql_connect( $databaseServer, $databaseUsername, $databasePassword ) + or dps_fatalError( "Could not connect to database server: " . + mysql_error() ); + + mysql_select_db( $databaseName ) + or dps_fatalError( "Could not select $databaseName database: " . + mysql_error() ); + } + + + +/** + * Closes the database connection. + */ +function dps_closeDatabase() { + mysql_close(); + } + + + +/** + * Queries the database, and dies with an error message on failure. + * + * @param $inQueryString the SQL query string. + * + * @return a result handle that can be passed to other mysql functions. + */ +function dps_queryDatabase( $inQueryString ) { + + $result = mysql_query( $inQueryString ) + or dps_fatalError( "Database query failed:
$inQueryString

" . + mysql_error() ); + + return $result; + } + + + +/** + * Checks whether a table exists in the currently-connected database. + * + * @param $inTableName the name of the table to look for. + * + * @return 1 if the table exists, or 0 if not. + */ +function dps_doesTableExist( $inTableName ) { + // check if our table exists + $tableExists = 0; + + $query = "SHOW TABLES"; + $result = dps_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + + for( $i=0; $i<$numRows && ! $tableExists; $i++ ) { + + $tableName = mysql_result( $result, $i, 0 ); + + if( $tableName == $inTableName ) { + $tableExists = 1; + } + } + return $tableExists; + } + + + +function dps_log( $message ) { + global $enableLog, $tableNamePrefix; + + $slashedMessage = addslashes( $message ); + + if( $enableLog ) { + $query = "INSERT INTO $tableNamePrefix"."log VALUES ( " . + "'$slashedMessage', CURRENT_TIMESTAMP );"; + $result = dps_queryDatabase( $query ); + } + } + + + +/** + * Displays the error page and dies. + * + * @param $message the error message to display on the error page. + */ +function dps_fatalError( $message ) { + //global $errorMessage; + + // set the variable that is displayed inside error.php + //$errorMessage = $message; + + //include_once( "error.php" ); + + // for now, just print error message + $logMessage = "Fatal error: $message"; + + echo( $logMessage ); + + dps_log( $logMessage ); + + die(); + } + + + +/** + * Displays the operation error message and dies. + * + * @param $message the error message to display. + */ +function dps_operationError( $message ) { + + // for now, just print error message + echo( "ERROR: $message" ); + die(); + } + + +/** + * Recursively applies the addslashes function to arrays of arrays. + * This effectively forces magic_quote escaping behavior, eliminating + * a slew of possible database security issues. + * + * @inValue the value or array to addslashes to. + * + * @return the value or array with slashes added. + */ +function dps_addslashes_deep( $inValue ) { + return + ( is_array( $inValue ) + ? array_map( 'dps_addslashes_deep', $inValue ) + : addslashes( $inValue ) ); + } + + + +/** + * Recursively applies the stripslashes function to arrays of arrays. + * This effectively disables magic_quote escaping behavior. + * + * @inValue the value or array to stripslashes from. + * + * @return the value or array with slashes removed. + */ +function dps_stripslashes_deep( $inValue ) { + return + ( is_array( $inValue ) + ? array_map( 'sb_stripslashes_deep', $inValue ) + : stripslashes( $inValue ) ); + } + + + +function dps_checkPassword( $inFunctionName ) { + $password = ""; + if( isset( $_REQUEST[ "password" ] ) ) { + $password = $_REQUEST[ "password" ]; + } + + global $accessPassword, $tableNamePrefix, $remoteIP; + + if( $password != $accessPassword ) { + echo "Incorrect password."; + + dps_log( "Failed $inFunctionName access with password: $password" ); + + die(); + } + + return $password; + } + + +?> diff --git a/demo/settings.php b/demo/settings.php new file mode 100644 index 0000000..3d50383 --- /dev/null +++ b/demo/settings.php @@ -0,0 +1,46 @@ + \ No newline at end of file diff --git a/documentation/Readme.txt b/documentation/Readme.txt new file mode 100644 index 0000000..f01085e --- /dev/null +++ b/documentation/Readme.txt @@ -0,0 +1,52 @@ +Sleep is Death (Geisterfahrer) +An asymmetric storytelling game for two players by Jason Rohrer + +http://sleepisdeath.net + + + + +Designed for a LAN: + +1. Set up two computers on your local network (WiFi or Wire). + +2. Plugging two computers directly together with a single Ethernet cable also + works. + +3. Start one as Controller. + +4. Give the Player the Controller's IP address as displayed by the game. + +5. If the Controller and Player are in the same room, give the Controller + headphones. + +6. Don't look at each other's screens. + +7. Enjoy each other's company. + + + +Possible to play over the Internet: + +1. Player should work fine with any reliable Internet connection. + +2. Controller must be able to receive incoming connections. + +3. Controller tries to use UPnP to open a port on your router and determine + your external IP address. This should work automatically on most modern + routers. + + +4. If UPnP doesn't work: + +5. By default, Controller listens on port 7778. + +6. Set up Port Forwarding, DMZ, or Static IP for the Controller on your DSL + or Cable Modem's router. + +5. Give the Player your real, EXTERNAL IP address. + Try a service like http://www.whatismyip.com/ + Probably does not start with 192.168... (that's your internal IP). + +6. If 7778 doesn't work for you, change it in settings/port.ini + Make sure the Player changes his or her port.ini to match yours. \ No newline at end of file diff --git a/documentation/Upgrading.txt b/documentation/Upgrading.txt new file mode 100644 index 0000000..dfc28be --- /dev/null +++ b/documentation/Upgrading.txt @@ -0,0 +1,40 @@ +Instructions for upgrading from previous versions to v16. + +To transfer your resources across: + +1. PLEASE make and keep a backup of your old game directory. I would be sad if + you lost any of your hard work, through your error or my error. + +2. COPY (don't move) your old "resourceCache" folder INTO the "importOldCache" + folder in v16. + +3. Start the v16 game, and enter Controller mode. This will "pause" for + longer than normal after you hit "G" (before the Controller mode is shown), + while your old resources are merged and upgraded. This will only happen + once, so be patient. + +4. After the Controller mode loads, check that your resources have been + imported correctly. + + +v16 is compatible in every way with v15. + + + +v13 Packs can be loaded into v16, but they will trigger a similar pause at +startup (while they are converted). + +v14 Packs can be loaded into v16. + + +v15/v16 Packs are NOT compatible with v13. + +v15/v16 Packs are compatible with v14 as long as they do not contain +Color Palette resources. + + +v16 should be able to both host and play against v14. + +v16 CANNOT host games for a v13 player (the objects sent across now have more +information in them that v13 cannot handle). v13 should be able to host a +v16 player, however. diff --git a/documentation/changeLog.txt b/documentation/changeLog.txt new file mode 100644 index 0000000..3d6d0b7 --- /dev/null +++ b/documentation/changeLog.txt @@ -0,0 +1,508 @@ +Version 16 2010-November-15 + +--Improved responsiveness of mouse and keyboard controls. + +--More accurate frame timing for true 30 frames-per-second when possible. + +--Enabled GL swap control to minimize visual tearing. + +--Added auto-removal of bad search database entries when they are encountered. + +--Changed to ignore duplicate search database entries. + +--Added "used in" tool tip for resources that cannot be deleted due to usage. + + + + +Version 15b 2010-June-10 + +--Rolled back to older Windows SDL version (1.2.13), fixing international + keyboard input issues. + +--Fixed reversed o accents in font image file. + + + + +Version 15 2010-May-28 + +--Fixed crash when getting local address on certain platforms with certain + network setups (VPN). + +--Fixed crash when scale reduced to zero tones. + +--Added missing tool tip for Scene deletion. + +--Improved build time by making dependency tracking modular. + +--Added usage tracking to prevent accidental deletion of resources that are + being used by other resources (such as objects still used in some scenes). + +--Fixed a Controller freeze triggered by a Player that previously ran in + Controller mode in the same session. + +--Improved Unix source build scripts (thanks to Andy Sommerville). + +--Window close button now functions properly in windowed mode (thanks to + Andy Sommerville). + +--Fixed freeze on Player end when Controller sends a Song referencing parts + that have been deleted. + +--Added Extended ASCII support for non-US accents and characters. Most + western-European languages should be supported by the included font image + (ISO/8859-1, Latin-1). Other languages (such as those that use Cyrillic) + can be used by modifying the font image. + +--Added Color Palettes as first-class resources with their own Picker. + +--Fixed many compile warnings thrown by certain versions of GCC. + +--Fixed bug in rubber stamp tool when dealing with transparent areas. + +--Improved appearance of grid in Room Editor. + +--Improved grid lines in various pixel editors to reduce color distortions. + +--Added a note about ESC key to menu screen. + +--Can now use mouse, in addition to arrow keys, on menu screen. + +--Shows port number on manual connection screen. + +--New object position hints in room editor (helpful when placing wall flags + around objects). + +--Added IP (and IP:port) command-line argument to auto-join a hosted game. + +--Added memory of last set speech bubble position for each object, alleviating + the need to manually set speech bubble positions most of the time. + +--Added tool tips for each tile in Room Editor grid. + +--Added check for resouceCache write permissions. + +--Added a button for holding certain objects across scene changes. + +--Added a button for colorizing all pixels of a sprite or tile. + +--Ctrl-Q can be used to quit (in addition to ESC). + +--Fixed a bug that caused control key to stick when opening editor with + keyboard shortcuts. + +--Ctrl-A to add a new sprite layer to an object at current mouse location + (great for adding many of the same sprite, like tree leaves). + +--Ctrl-A to add a new object to a scene at current mouse location + (great for adding many of the same objects, like flowers). + +--Improved object editor mouse input so that only dragging moves a layer + (clicking on an empty spot no longer moves a layer). + +--Added buttons for moving a sprite layer up and down in the Object Editor. + + + +Version 14b 2010-April-23 + +--Fixed import cache hang when a referenced resource does not exist. + +--Fixed line endings in stringDatabase.txt on windows. + +--Fixed long loading times when importing resource packs. + +--Improved loading times when importing an old resource cache. + +--Added a loading screen. + +--Fixed a glitch where Waiting message not shown after joining a second game as Player. + +--Reduced audio buffer size to improve latency (was increased during v14 + debugging). + +--Added missing "add to pack" button for Songs, removed for Phrases. + +--Removed wayward "edit timbre" button from Phrase Editor. + +--Added Ctrl-E to edit selected object or selected sprite layer. + + + + +Version 14 2010-April-20 + +--Fixed memory allocation bug in unique IDs. + +--Fixed code to be 64-bit clean. + +--Fixed bug in adding resources to a picker stack when that stack is visible. + +--Fixed crash when undoing back to default object in Object editor. + +--Fixed flip book sizing and distortion issues when screen is not 640x480 + +--Overhauled music editor, making it much more powerful. + +--Fixed bug in selection of previously colored but erased sprite areas. + +--Improved transparency toggle tool tip in Object editor. + +--Added Ctrl-W to close editors. + +--Added Alt-Enter to leave fullscreen mode (as work-around for lack of working + alt-tab behavior on the Mac). + +--Added an import directory to make upgrading easier. + +--Made UPNP messages more clear. + +--Cleared external address if going back to hosting a local game. + +--Added per-layer additive blend mode (for glowing objects). + +--Search results with same name are now shown in newest-first order. + + + +Version 13 2010-April-9 + +--Fixed crash when opening and closing color editor repeatedly. + +--Fixed glitch in dragging to edit room wall markers. + +--Fixed a glitch in editing selection for stamp tool. + +--Fixed keyboard event bug in Practice mode. + +--Changed flashing warning display so it always on top (was blocked by color + palette). + +--Fixed quit confirmation to linger on the screen even if mouse moved. + +--Fixed screen size bug when user specifies an unsupported resolution for + his or her monitor. + +--Changed so that background tile (for pickers and object editor) does not + follow tile chosen in room editor. Instead, it always matches the background + tile under the currently selected object in the scene. + +--Changed so that speech bubble moving tool cannot change selected object. + + + +Version 12 2010-April-8 + +--Fixed timing bug that allowed duplicate moves to be sent, leading to + out-of-synch games + +--Fixed crash (and misbehavior) when dragging whole object in object editor + starting from off the edge of the display. + +--Fixed behavior when dragging between sliders and buttons. Sliders can + now be dragged around wildly without affecting nearby components. + +--Dragging around in color palette changes selected color well. + +--Improved focus behavior of text fields on menu screen. + +--Now Controller interface holds keyboard focus on speech bubbles no matter + where the user clicks on display (unless typing in a text field). + Typing always adds to current object's speech now. + +--Fixed a crash when more than 255 layers added to an object. + +--Added better user feedback when an object is full. + +--Added better user feedback when a scene is full. + +--Tool tips and timer now on top of all scenery. + +--Practice mode now outputs flipbooks too (solo play?). + +--Blocked Controller typing into Player's bubble, since it's not sent anyway. + +--Fixed Picker background tile being updated due to keyboard events. + +--No longer treats mouse wheel events as mouse clicks. + +--Fixed crash when resourceCache foldernot preset---makes a new folder + if needed. + +--Moved stringDatabase.txt into resourceCache folder. + +--Fixed a slow-down when dragging large objects across tile boundaries. + +--Changed to hide selected layer flashing border when dragging layer (for + precise positioning). + +--Added checksum to network protocol, fixing a crash when bad network data + received. + +--Updated all bundled v9 resource files to the v10+ format. + + + + + +Version 11 2010-April-7 + +--Fixed drag and drop crash bug in object editor. + +--Fixed a line-end bug in windows fullscreen settings file. + + + + + +Version 10 2010-April-7 + +--Changed OpenGL color functions to work around a bug in some video drivers. + +--Added key-repeat when holding down a key (deleting text is easier). + +--Added text boxes in addition to speech bubbles. + +--Added an object locking feature that makes it easier to manipulate complex + scenes. + +--Made Close button infinite to upper left. + +--Switched to an HSV color picker (from RGB). + +--Replaced color stack with a collection of editable color wells. + +--Added rotate and flip buttons in object editor. + +--Added resource packs for sharing resources through email and web. + +--Added disk-based cache of thumbnail images to speed up resource pickers. + +--Improved thumbnail view of Scenes with small rooms. + +--Improved centering of room thumbnails. + +--Fixed timing inconsistencies (30 seconds is now always 30 seconds). + +--Fixed resizing and mouse handling for screen sizes other than 640x480. + +--Moved flip book PNGs into an images subdir. Made file names order-proof. + +--Added pure HTML version of flip books for easy local viewing. + +--Added a per-layer transparency to objects. + +--Fixed rendering of object transparencies in scene thumbnails. + +--Fixed event passing bug when new editor opened. + +--Made text fields easier to click. + +--Changed default mode to speech when player position frozen. + +--Added a practice mode against timer for Controller. + +--Speech delete button on Controller side now clear's Player's action too. + +--Added settings file for time limit. + +--Moved all printed output to a log file. + +--Ctrl-X mapped to delete for scene objects and object layers. + +--Added confirmation on quit (after pressing Escape). + +--Fixed a movement glitch when player types before moving. + +--Improved highlights of selected object layers. + +--Fixed jitter when speech bubble auto-flips. + +--Added any-key message to bottom of volume screen. + +--Background tile in picker now set properly when object added or removed. + +--Speech flip button enabled properly after speech deleted. + + + + + +Version 9 2010-March-30 + +--Fixed freeze bug when objects requested across wire that have been deleted. + +--Fixed image cache of deleted objects. + +--Removed multiple fetching of same new object in one batch network request. + +--Action arrows fixed to never go off sides of screen. + +--Re-wrote word-wrapping algorithm for speech bubbles to handle spacing + automatically and hyphenate words that are too long for one line. + +--Fixed bug in speech flip button appearing when it can't be used. + + + + +Version 8 2010-March-29 + +--Fixed position of Clear button in room editor. + +--Improved search box behavior for multi-word searches. + +--Fixed a drag-and-drop bug that occured accross editors. + +--Removed unnecessary object descriptor tool tips on Player side. + +--Added dynamic bubble tails to keep speech from going off top or sides + of screen. + +--Fixed bug that made Player's speech not deleteable on Controller side. + +--Improved sizing of verb box with single-pixel increments. + +--Typing on Player's side automatically adds speech, even if in Move mode. + +--Player's Speech and Action Delete buttons are now modeless. + + + + +Version 7 2010-March-25 + +--Disabled player send button on initial turn waiting screen, fixing a + potential out-of-synch issue. + +--Added UPNP support for opening ports on routers and obtaining the external + IP address (makes remote internet games easier to set up). + +--ESC from inside the game now takes you back to the menu where you can + start a new game. + +--Added Scenes (rooms with placed objects) as first-class resources. + +--Got rid of redundant "new object" mode on game state toolbar. + +--Improved object selection behavior when objects overlap. + +--Added an "add object" button. + +--Added button for editing the selected object directly. + +--Improved arrangement of state editor UI. + +--Removed flipbook output toggle button. Flipbooks always on now. + +--Improved arrangement of object editor UI. + +--Added button for editing the selected sprite layer directly. + +--Added support for moving all sprite layers together relative to object + anchor. + +--Added support for dragging and dropping all layers from one object into + another. + +--Controller editing of selected object's speech now happens whenever you type, + regardless of what mode you are in. + +--Added confirmation for Send button. + + + +Version 6 2010-March-21 + +--Drag-and-drop from object picker to add objects to scene. + +--Selected sprite layer blinks in object editor. + +--Fixed empty verb arrows that appeared in slide show when player frozen. + +--Sprite Picker selected sprite now follows current sprite layer in object + editor. Got rid of redundant Grab Layer button. + +--Added missing warning display to music editor. + +--Increased to 10 bookmarks, fixed add button, added mnemonic colors. + +--Locked keyboard focus even if player clicks off main game display area. + +--Fixed double-space issue in speech bubbles. + +--Fleshed out music editor with more functionality. + +--Graceful failure if game folder is read-only. + + + + +Version 5 2010-March-18 + +--Fixed bug in player send behavior and stale speech clearing. + +--Redesign of sprite mask editing to make it more intuitive. + +--Basic keyboard commands (shift-click to pick color, ctrl-z to undo) + +--Click to pick object sprite layer in object editor. + +--Drag-and-drop from sprite picker into object editor. + +--Flipbook output now on by default. + + + + +Version 4 2010-March-7 + +--Controller can now receive subsequent Player connections after initial Player + quits. + +--Highlight speech bubble that is currently being edited. + +--Auto-choose background tile for picker displays using current object position + in game. + +--Slide-show output. + +--Player send button. + +--Game state bookmarks. + +--Buttons for jumping to top/bottom object layers. + +--Object depth sorting. + +--Object anchors more visible. + +--Warning display for controller (important game events). + +--Autoclearing stale speech. + + + + +Version 3 2010-February-11 + +--Added fade sliders for objects and room. + +--Fixed 1 pixel text overlap in speech bubbles (added more leading). + +--Fixed undo behavior of switching selected object. + +--Pickers now update properly when game state is cleared. + +--Added auto-host and auto-join modes to make startup automatic in a gallery + setting. + +--Fixed room editor mini view when clear button pressed. + + + + +Version 2 2010-February-1 + +Initial release for Art History of Games conference. \ No newline at end of file diff --git a/documentation/guides/controller/Makefile b/documentation/guides/controller/Makefile new file mode 100644 index 0000000..8c72183 --- /dev/null +++ b/documentation/guides/controller/Makefile @@ -0,0 +1,29 @@ + + +# +# Generic: +# +# Map all .cpp C++ and C files into .o object files +# +# $@ represents the name.o file +# $< represents the name.cpp file +# +%.eps: %.png + convert $< $@ +%.eps: %.sk + sk2ps $< $@ +%.dvi: %.tex + latex $< +%.ps: %.dvi + dvips -Ppdf -t letter -o $@ $< +%.pdf: %.ps + ps2pdf $< + + +all: controllerGuide.pdf + +clean: + rm *.pdf *.ps *.dvi *.log *.aux + + +controllerGuide.dvi: screen.eps close.eps stack.eps search.eps replace.eps edit.eps clear.eps \ No newline at end of file diff --git a/documentation/guides/controller/clear.png b/documentation/guides/controller/clear.png new file mode 100644 index 0000000..688a35f Binary files /dev/null and b/documentation/guides/controller/clear.png differ diff --git a/documentation/guides/controller/close.png b/documentation/guides/controller/close.png new file mode 100644 index 0000000..345782e Binary files /dev/null and b/documentation/guides/controller/close.png differ diff --git a/documentation/guides/controller/controllerGuide.tex b/documentation/guides/controller/controllerGuide.tex new file mode 100644 index 0000000..373484c --- /dev/null +++ b/documentation/guides/controller/controllerGuide.tex @@ -0,0 +1,63 @@ +\documentclass[12pt]{article} +\usepackage{amssymb,latexsym,fullpage,epsfig} + + + +\newcommand{\buttonFig}[1]{ +\parbox[c]{1em}{\vspace{0em} \includegraphics[width=1em]{#1} } +} + +\begin{document} + + +\title{Sleep Is Death (Geisterfahrer)} + +\date{} + +\maketitle +%\thispagestyle{empty} + + +\vspace{-0.9in} + +\begin{center} +{\Large \it Controller Interface Guide} +\end{center} + + +%\section{Overview} +{\it Sleep Is Death} is a two-player, turn-based game. One person plays as the ``Player,'' and the other plays as the ``Controller.'' + +The Player commands the behavior of a single character in the world. Hopefully, the world and other objects react coherently to the Player's actions---it is the Controller's job to ensure this coherence. The Controller commands the behavior of all the other characters and objects in the world, essentially fabricating an interactive illusion that folds around the Player. + +%The user interface shown to the Player is too simple to warrent explanation. The Controller's interface, on the other hand, is quite complex. However, the Controller's interface was designed to be as simple as possible while still allowing the Controller to {\it quickly} manipulate every aspect of the game world. + +The Controller's interface is comprehensive. Swapping rooms, swapping objects, editing speech, moving things around, and editing the soundtrack are all at your fingertips. Deeper levels of the interface also support editing rooms and objects on the fly, a technique that becomes necessary for advanced Controller play. + +The basic Controller interface is show below: +\begin{center} +\includegraphics[width=0.6\textwidth]{screen.eps} +\end{center} +If your screen doesn't look like this, you may be down inside one of the sub-editors. Look for a Close button (\buttonFig{close.eps}) in the upper-left corner of the screen. Press Close until you back out to the basic screen shown above. If you want to return to a ``clean slate'' at any point, press the Clear button: +\begin{center} +\includegraphics[width=3em]{clear.eps} +\end{center} +Tool tips are provided for every button in the game. Some of the more subtle aspects of Controller play are described on the following page. + + +\paragraph{Turn structure} Each side has {\it 30 seconds} to commit a move, one after the other. The timer for your move is shown in the upper left corner of the screen. When the counter reaches 0, your move is sent. Your counter goes negative during the other player's move, but jumps back up to positive 30 again when it's your turn. + +During the Controller's move time, the Player's view is frozen in a ``Waiting...'' state. The Controller manipulates the state of the world, and after the Controller's move is sent, the Player's view is updated to match that state. During the Player's move time, the Controller can continue to manipulate the world, behind the scenes, in anticipation of the move that the Player will eventually send. These behind-the-scenes manipulations are invisible to the Player. %Thus, while the Player has only 30 seconds to pick a move, the Controller effectively has 60 seconds. This extra time can be used to prepare new objects and rooms for potential future needs in the game. + +\paragraph{Game resources} Objects and Rooms in the game can be browsed and selected with the Picker interfaces on the right side of the screen. These interfaces can be toggled between Stack and Search modes with the \buttonFig{stack.eps} and \buttonFig{search.eps} buttons. The game state's Room will switch to whatever Room is selected in the picker. Objects are a bit different, though, as picking an Object doesn't automatically alter the game state. Instead, pressing this button will replace the current Object in the game state with the selected Object from the Picker: +\begin{center} +\includegraphics[width=3em]{replace.eps} +\end{center} + +\paragraph{Manipulating Objects} The toolbar at the bottom of the screen allows you to manipulate Objects in the game state in various ways. Undo and Redo buttons are also provided, so there's no danger in making a mistake. + +\paragraph{Going deeper} Every resource in the game is fully editable, should the need arise. Look for the Edit buttons (\buttonFig{edit.eps}) at the top of each picker to enter a sub-editor. Some of these have further sub-editors. Whenever you edit a resource, your new version of that resource is added to the database. You can then use your new version in the current game state by backing out to the main state editor (press the Close button, \buttonFig{close.eps}, in the upper left corner). + + + +\end{document} diff --git a/documentation/guides/controller/edit.png b/documentation/guides/controller/edit.png new file mode 100644 index 0000000..510e17e Binary files /dev/null and b/documentation/guides/controller/edit.png differ diff --git a/documentation/guides/controller/replace.png b/documentation/guides/controller/replace.png new file mode 100644 index 0000000..c2643af Binary files /dev/null and b/documentation/guides/controller/replace.png differ diff --git a/documentation/guides/controller/screen.png b/documentation/guides/controller/screen.png new file mode 100644 index 0000000..48a598d Binary files /dev/null and b/documentation/guides/controller/screen.png differ diff --git a/documentation/guides/controller/search.png b/documentation/guides/controller/search.png new file mode 100644 index 0000000..5f2677d Binary files /dev/null and b/documentation/guides/controller/search.png differ diff --git a/documentation/guides/controller/stack.png b/documentation/guides/controller/stack.png new file mode 100644 index 0000000..c0c28e9 Binary files /dev/null and b/documentation/guides/controller/stack.png differ diff --git a/documentation/html/AreWeHome/actualScreenshot.png b/documentation/html/AreWeHome/actualScreenshot.png new file mode 100644 index 0000000..2f01c98 Binary files /dev/null and b/documentation/html/AreWeHome/actualScreenshot.png differ diff --git a/documentation/html/AreWeHome/footer.php b/documentation/html/AreWeHome/footer.php new file mode 100644 index 0000000..4313d4d --- /dev/null +++ b/documentation/html/AreWeHome/footer.php @@ -0,0 +1,29 @@ + + +"; + +if( $imageNumber == 1 ) { + + ?> + + + + + + "; + + +?> + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/frameList.php b/documentation/html/AreWeHome/frameList.php new file mode 100644 index 0000000..5661c28 --- /dev/null +++ b/documentation/html/AreWeHome/frameList.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/documentation/html/AreWeHome/header.php b/documentation/html/AreWeHome/header.php new file mode 100644 index 0000000..9ebebd9 --- /dev/null +++ b/documentation/html/AreWeHome/header.php @@ -0,0 +1,13 @@ + + + +Sleep Is Death Flip Book + + + + + +[home] [order] [start] + +
\ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00001.html b/documentation/html/AreWeHome/images/00001.html new file mode 100644 index 0000000..baa1823 --- /dev/null +++ b/documentation/html/AreWeHome/images/00001.html @@ -0,0 +1,44 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + +
+ + +
+ + +
+ + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00001.png b/documentation/html/AreWeHome/images/00001.png new file mode 100644 index 0000000..76a2b14 Binary files /dev/null and b/documentation/html/AreWeHome/images/00001.png differ diff --git a/documentation/html/AreWeHome/images/00002.html b/documentation/html/AreWeHome/images/00002.html new file mode 100644 index 0000000..05f1394 --- /dev/null +++ b/documentation/html/AreWeHome/images/00002.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00002.png b/documentation/html/AreWeHome/images/00002.png new file mode 100644 index 0000000..9326e2a Binary files /dev/null and b/documentation/html/AreWeHome/images/00002.png differ diff --git a/documentation/html/AreWeHome/images/00003.html b/documentation/html/AreWeHome/images/00003.html new file mode 100644 index 0000000..ceda53c --- /dev/null +++ b/documentation/html/AreWeHome/images/00003.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00003.png b/documentation/html/AreWeHome/images/00003.png new file mode 100644 index 0000000..757a57e Binary files /dev/null and b/documentation/html/AreWeHome/images/00003.png differ diff --git a/documentation/html/AreWeHome/images/00004.html b/documentation/html/AreWeHome/images/00004.html new file mode 100644 index 0000000..6834efb --- /dev/null +++ b/documentation/html/AreWeHome/images/00004.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00004.png b/documentation/html/AreWeHome/images/00004.png new file mode 100644 index 0000000..dd2d680 Binary files /dev/null and b/documentation/html/AreWeHome/images/00004.png differ diff --git a/documentation/html/AreWeHome/images/00005.html b/documentation/html/AreWeHome/images/00005.html new file mode 100644 index 0000000..58632c4 --- /dev/null +++ b/documentation/html/AreWeHome/images/00005.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00005.png b/documentation/html/AreWeHome/images/00005.png new file mode 100644 index 0000000..e8eeee4 Binary files /dev/null and b/documentation/html/AreWeHome/images/00005.png differ diff --git a/documentation/html/AreWeHome/images/00006.html b/documentation/html/AreWeHome/images/00006.html new file mode 100644 index 0000000..df34bdb --- /dev/null +++ b/documentation/html/AreWeHome/images/00006.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00006.png b/documentation/html/AreWeHome/images/00006.png new file mode 100644 index 0000000..c8388bb Binary files /dev/null and b/documentation/html/AreWeHome/images/00006.png differ diff --git a/documentation/html/AreWeHome/images/00007.html b/documentation/html/AreWeHome/images/00007.html new file mode 100644 index 0000000..24433ff --- /dev/null +++ b/documentation/html/AreWeHome/images/00007.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00007.png b/documentation/html/AreWeHome/images/00007.png new file mode 100644 index 0000000..577cff3 Binary files /dev/null and b/documentation/html/AreWeHome/images/00007.png differ diff --git a/documentation/html/AreWeHome/images/00008.html b/documentation/html/AreWeHome/images/00008.html new file mode 100644 index 0000000..7887e04 --- /dev/null +++ b/documentation/html/AreWeHome/images/00008.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00008.png b/documentation/html/AreWeHome/images/00008.png new file mode 100644 index 0000000..7a4cd7d Binary files /dev/null and b/documentation/html/AreWeHome/images/00008.png differ diff --git a/documentation/html/AreWeHome/images/00009.html b/documentation/html/AreWeHome/images/00009.html new file mode 100644 index 0000000..171b2c1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00009.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00009.png b/documentation/html/AreWeHome/images/00009.png new file mode 100644 index 0000000..04d15c3 Binary files /dev/null and b/documentation/html/AreWeHome/images/00009.png differ diff --git a/documentation/html/AreWeHome/images/00010.html b/documentation/html/AreWeHome/images/00010.html new file mode 100644 index 0000000..e819002 --- /dev/null +++ b/documentation/html/AreWeHome/images/00010.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00010.png b/documentation/html/AreWeHome/images/00010.png new file mode 100644 index 0000000..e53af7f Binary files /dev/null and b/documentation/html/AreWeHome/images/00010.png differ diff --git a/documentation/html/AreWeHome/images/00011.html b/documentation/html/AreWeHome/images/00011.html new file mode 100644 index 0000000..8acbf76 --- /dev/null +++ b/documentation/html/AreWeHome/images/00011.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00011.png b/documentation/html/AreWeHome/images/00011.png new file mode 100644 index 0000000..c512230 Binary files /dev/null and b/documentation/html/AreWeHome/images/00011.png differ diff --git a/documentation/html/AreWeHome/images/00012.html b/documentation/html/AreWeHome/images/00012.html new file mode 100644 index 0000000..780d371 --- /dev/null +++ b/documentation/html/AreWeHome/images/00012.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00012.png b/documentation/html/AreWeHome/images/00012.png new file mode 100644 index 0000000..bf31a17 Binary files /dev/null and b/documentation/html/AreWeHome/images/00012.png differ diff --git a/documentation/html/AreWeHome/images/00013.html b/documentation/html/AreWeHome/images/00013.html new file mode 100644 index 0000000..be05671 --- /dev/null +++ b/documentation/html/AreWeHome/images/00013.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00013.png b/documentation/html/AreWeHome/images/00013.png new file mode 100644 index 0000000..78789a1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00013.png differ diff --git a/documentation/html/AreWeHome/images/00014.html b/documentation/html/AreWeHome/images/00014.html new file mode 100644 index 0000000..79aa83c --- /dev/null +++ b/documentation/html/AreWeHome/images/00014.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00014.png b/documentation/html/AreWeHome/images/00014.png new file mode 100644 index 0000000..1a4b9ee Binary files /dev/null and b/documentation/html/AreWeHome/images/00014.png differ diff --git a/documentation/html/AreWeHome/images/00015.html b/documentation/html/AreWeHome/images/00015.html new file mode 100644 index 0000000..6572f1b --- /dev/null +++ b/documentation/html/AreWeHome/images/00015.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00015.png b/documentation/html/AreWeHome/images/00015.png new file mode 100644 index 0000000..3fd3123 Binary files /dev/null and b/documentation/html/AreWeHome/images/00015.png differ diff --git a/documentation/html/AreWeHome/images/00016.html b/documentation/html/AreWeHome/images/00016.html new file mode 100644 index 0000000..64d37e2 --- /dev/null +++ b/documentation/html/AreWeHome/images/00016.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00016.png b/documentation/html/AreWeHome/images/00016.png new file mode 100644 index 0000000..61926f3 Binary files /dev/null and b/documentation/html/AreWeHome/images/00016.png differ diff --git a/documentation/html/AreWeHome/images/00017.html b/documentation/html/AreWeHome/images/00017.html new file mode 100644 index 0000000..990dfcc --- /dev/null +++ b/documentation/html/AreWeHome/images/00017.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00017.png b/documentation/html/AreWeHome/images/00017.png new file mode 100644 index 0000000..71775f7 Binary files /dev/null and b/documentation/html/AreWeHome/images/00017.png differ diff --git a/documentation/html/AreWeHome/images/00018.html b/documentation/html/AreWeHome/images/00018.html new file mode 100644 index 0000000..7f70485 --- /dev/null +++ b/documentation/html/AreWeHome/images/00018.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00018.png b/documentation/html/AreWeHome/images/00018.png new file mode 100644 index 0000000..1e5cfc5 Binary files /dev/null and b/documentation/html/AreWeHome/images/00018.png differ diff --git a/documentation/html/AreWeHome/images/00019.html b/documentation/html/AreWeHome/images/00019.html new file mode 100644 index 0000000..3275ed9 --- /dev/null +++ b/documentation/html/AreWeHome/images/00019.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00019.png b/documentation/html/AreWeHome/images/00019.png new file mode 100644 index 0000000..17694cb Binary files /dev/null and b/documentation/html/AreWeHome/images/00019.png differ diff --git a/documentation/html/AreWeHome/images/00020.html b/documentation/html/AreWeHome/images/00020.html new file mode 100644 index 0000000..b4fedd5 --- /dev/null +++ b/documentation/html/AreWeHome/images/00020.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00020.png b/documentation/html/AreWeHome/images/00020.png new file mode 100644 index 0000000..0b35a5c Binary files /dev/null and b/documentation/html/AreWeHome/images/00020.png differ diff --git a/documentation/html/AreWeHome/images/00021.html b/documentation/html/AreWeHome/images/00021.html new file mode 100644 index 0000000..3c752ac --- /dev/null +++ b/documentation/html/AreWeHome/images/00021.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00021.png b/documentation/html/AreWeHome/images/00021.png new file mode 100644 index 0000000..bd5d04b Binary files /dev/null and b/documentation/html/AreWeHome/images/00021.png differ diff --git a/documentation/html/AreWeHome/images/00022.html b/documentation/html/AreWeHome/images/00022.html new file mode 100644 index 0000000..aa6cce0 --- /dev/null +++ b/documentation/html/AreWeHome/images/00022.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00022.png b/documentation/html/AreWeHome/images/00022.png new file mode 100644 index 0000000..045cbe8 Binary files /dev/null and b/documentation/html/AreWeHome/images/00022.png differ diff --git a/documentation/html/AreWeHome/images/00023.html b/documentation/html/AreWeHome/images/00023.html new file mode 100644 index 0000000..73b7668 --- /dev/null +++ b/documentation/html/AreWeHome/images/00023.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00023.png b/documentation/html/AreWeHome/images/00023.png new file mode 100644 index 0000000..4bfa842 Binary files /dev/null and b/documentation/html/AreWeHome/images/00023.png differ diff --git a/documentation/html/AreWeHome/images/00024.html b/documentation/html/AreWeHome/images/00024.html new file mode 100644 index 0000000..a702a4c --- /dev/null +++ b/documentation/html/AreWeHome/images/00024.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00024.png b/documentation/html/AreWeHome/images/00024.png new file mode 100644 index 0000000..08473ac Binary files /dev/null and b/documentation/html/AreWeHome/images/00024.png differ diff --git a/documentation/html/AreWeHome/images/00025.html b/documentation/html/AreWeHome/images/00025.html new file mode 100644 index 0000000..504cc77 --- /dev/null +++ b/documentation/html/AreWeHome/images/00025.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00025.png b/documentation/html/AreWeHome/images/00025.png new file mode 100644 index 0000000..2bd6f6f Binary files /dev/null and b/documentation/html/AreWeHome/images/00025.png differ diff --git a/documentation/html/AreWeHome/images/00026.html b/documentation/html/AreWeHome/images/00026.html new file mode 100644 index 0000000..0ac76e4 --- /dev/null +++ b/documentation/html/AreWeHome/images/00026.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00026.png b/documentation/html/AreWeHome/images/00026.png new file mode 100644 index 0000000..e3b7d55 Binary files /dev/null and b/documentation/html/AreWeHome/images/00026.png differ diff --git a/documentation/html/AreWeHome/images/00027.html b/documentation/html/AreWeHome/images/00027.html new file mode 100644 index 0000000..0ded82a --- /dev/null +++ b/documentation/html/AreWeHome/images/00027.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00027.png b/documentation/html/AreWeHome/images/00027.png new file mode 100644 index 0000000..080aaa6 Binary files /dev/null and b/documentation/html/AreWeHome/images/00027.png differ diff --git a/documentation/html/AreWeHome/images/00028.html b/documentation/html/AreWeHome/images/00028.html new file mode 100644 index 0000000..fe8ec75 --- /dev/null +++ b/documentation/html/AreWeHome/images/00028.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00028.png b/documentation/html/AreWeHome/images/00028.png new file mode 100644 index 0000000..b6547d1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00028.png differ diff --git a/documentation/html/AreWeHome/images/00029.html b/documentation/html/AreWeHome/images/00029.html new file mode 100644 index 0000000..12352b1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00029.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00029.png b/documentation/html/AreWeHome/images/00029.png new file mode 100644 index 0000000..e960a49 Binary files /dev/null and b/documentation/html/AreWeHome/images/00029.png differ diff --git a/documentation/html/AreWeHome/images/00030.html b/documentation/html/AreWeHome/images/00030.html new file mode 100644 index 0000000..46b3a02 --- /dev/null +++ b/documentation/html/AreWeHome/images/00030.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00030.png b/documentation/html/AreWeHome/images/00030.png new file mode 100644 index 0000000..fae158e Binary files /dev/null and b/documentation/html/AreWeHome/images/00030.png differ diff --git a/documentation/html/AreWeHome/images/00031.html b/documentation/html/AreWeHome/images/00031.html new file mode 100644 index 0000000..41f461d --- /dev/null +++ b/documentation/html/AreWeHome/images/00031.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00031.png b/documentation/html/AreWeHome/images/00031.png new file mode 100644 index 0000000..d8d6b0b Binary files /dev/null and b/documentation/html/AreWeHome/images/00031.png differ diff --git a/documentation/html/AreWeHome/images/00032.html b/documentation/html/AreWeHome/images/00032.html new file mode 100644 index 0000000..6733bc4 --- /dev/null +++ b/documentation/html/AreWeHome/images/00032.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00032.png b/documentation/html/AreWeHome/images/00032.png new file mode 100644 index 0000000..aaf8b3c Binary files /dev/null and b/documentation/html/AreWeHome/images/00032.png differ diff --git a/documentation/html/AreWeHome/images/00033.html b/documentation/html/AreWeHome/images/00033.html new file mode 100644 index 0000000..1e42ed8 --- /dev/null +++ b/documentation/html/AreWeHome/images/00033.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00033.png b/documentation/html/AreWeHome/images/00033.png new file mode 100644 index 0000000..edd81c3 Binary files /dev/null and b/documentation/html/AreWeHome/images/00033.png differ diff --git a/documentation/html/AreWeHome/images/00034.html b/documentation/html/AreWeHome/images/00034.html new file mode 100644 index 0000000..c6e0954 --- /dev/null +++ b/documentation/html/AreWeHome/images/00034.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00034.png b/documentation/html/AreWeHome/images/00034.png new file mode 100644 index 0000000..18d756a Binary files /dev/null and b/documentation/html/AreWeHome/images/00034.png differ diff --git a/documentation/html/AreWeHome/images/00035.html b/documentation/html/AreWeHome/images/00035.html new file mode 100644 index 0000000..d302c47 --- /dev/null +++ b/documentation/html/AreWeHome/images/00035.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00035.png b/documentation/html/AreWeHome/images/00035.png new file mode 100644 index 0000000..1252822 Binary files /dev/null and b/documentation/html/AreWeHome/images/00035.png differ diff --git a/documentation/html/AreWeHome/images/00036.html b/documentation/html/AreWeHome/images/00036.html new file mode 100644 index 0000000..b8edc12 --- /dev/null +++ b/documentation/html/AreWeHome/images/00036.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00036.png b/documentation/html/AreWeHome/images/00036.png new file mode 100644 index 0000000..f668524 Binary files /dev/null and b/documentation/html/AreWeHome/images/00036.png differ diff --git a/documentation/html/AreWeHome/images/00037.html b/documentation/html/AreWeHome/images/00037.html new file mode 100644 index 0000000..0328255 --- /dev/null +++ b/documentation/html/AreWeHome/images/00037.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00037.png b/documentation/html/AreWeHome/images/00037.png new file mode 100644 index 0000000..045662c Binary files /dev/null and b/documentation/html/AreWeHome/images/00037.png differ diff --git a/documentation/html/AreWeHome/images/00038.html b/documentation/html/AreWeHome/images/00038.html new file mode 100644 index 0000000..07a2674 --- /dev/null +++ b/documentation/html/AreWeHome/images/00038.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00038.png b/documentation/html/AreWeHome/images/00038.png new file mode 100644 index 0000000..d5c28d0 Binary files /dev/null and b/documentation/html/AreWeHome/images/00038.png differ diff --git a/documentation/html/AreWeHome/images/00039.html b/documentation/html/AreWeHome/images/00039.html new file mode 100644 index 0000000..b7d68b1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00039.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00039.png b/documentation/html/AreWeHome/images/00039.png new file mode 100644 index 0000000..b7489fe Binary files /dev/null and b/documentation/html/AreWeHome/images/00039.png differ diff --git a/documentation/html/AreWeHome/images/00040.html b/documentation/html/AreWeHome/images/00040.html new file mode 100644 index 0000000..c0939d3 --- /dev/null +++ b/documentation/html/AreWeHome/images/00040.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00040.png b/documentation/html/AreWeHome/images/00040.png new file mode 100644 index 0000000..23a43ee Binary files /dev/null and b/documentation/html/AreWeHome/images/00040.png differ diff --git a/documentation/html/AreWeHome/images/00041.html b/documentation/html/AreWeHome/images/00041.html new file mode 100644 index 0000000..b9267aa --- /dev/null +++ b/documentation/html/AreWeHome/images/00041.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00041.png b/documentation/html/AreWeHome/images/00041.png new file mode 100644 index 0000000..ebb6fd1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00041.png differ diff --git a/documentation/html/AreWeHome/images/00042.html b/documentation/html/AreWeHome/images/00042.html new file mode 100644 index 0000000..c3543d0 --- /dev/null +++ b/documentation/html/AreWeHome/images/00042.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00042.png b/documentation/html/AreWeHome/images/00042.png new file mode 100644 index 0000000..75fd4d1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00042.png differ diff --git a/documentation/html/AreWeHome/images/00043.html b/documentation/html/AreWeHome/images/00043.html new file mode 100644 index 0000000..42e2ae9 --- /dev/null +++ b/documentation/html/AreWeHome/images/00043.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00043.png b/documentation/html/AreWeHome/images/00043.png new file mode 100644 index 0000000..8a05bec Binary files /dev/null and b/documentation/html/AreWeHome/images/00043.png differ diff --git a/documentation/html/AreWeHome/images/00044.html b/documentation/html/AreWeHome/images/00044.html new file mode 100644 index 0000000..54d6d1a --- /dev/null +++ b/documentation/html/AreWeHome/images/00044.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00044.png b/documentation/html/AreWeHome/images/00044.png new file mode 100644 index 0000000..e9c95ba Binary files /dev/null and b/documentation/html/AreWeHome/images/00044.png differ diff --git a/documentation/html/AreWeHome/images/00045.html b/documentation/html/AreWeHome/images/00045.html new file mode 100644 index 0000000..851f8b8 --- /dev/null +++ b/documentation/html/AreWeHome/images/00045.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00045.png b/documentation/html/AreWeHome/images/00045.png new file mode 100644 index 0000000..558324a Binary files /dev/null and b/documentation/html/AreWeHome/images/00045.png differ diff --git a/documentation/html/AreWeHome/images/00046.html b/documentation/html/AreWeHome/images/00046.html new file mode 100644 index 0000000..b4d69a7 --- /dev/null +++ b/documentation/html/AreWeHome/images/00046.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00046.png b/documentation/html/AreWeHome/images/00046.png new file mode 100644 index 0000000..1c1d512 Binary files /dev/null and b/documentation/html/AreWeHome/images/00046.png differ diff --git a/documentation/html/AreWeHome/images/00047.html b/documentation/html/AreWeHome/images/00047.html new file mode 100644 index 0000000..3a69422 --- /dev/null +++ b/documentation/html/AreWeHome/images/00047.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00047.png b/documentation/html/AreWeHome/images/00047.png new file mode 100644 index 0000000..d4dd761 Binary files /dev/null and b/documentation/html/AreWeHome/images/00047.png differ diff --git a/documentation/html/AreWeHome/images/00048.html b/documentation/html/AreWeHome/images/00048.html new file mode 100644 index 0000000..89c7f8c --- /dev/null +++ b/documentation/html/AreWeHome/images/00048.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00048.png b/documentation/html/AreWeHome/images/00048.png new file mode 100644 index 0000000..e58eeef Binary files /dev/null and b/documentation/html/AreWeHome/images/00048.png differ diff --git a/documentation/html/AreWeHome/images/00049.html b/documentation/html/AreWeHome/images/00049.html new file mode 100644 index 0000000..f00ad25 --- /dev/null +++ b/documentation/html/AreWeHome/images/00049.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00049.png b/documentation/html/AreWeHome/images/00049.png new file mode 100644 index 0000000..9f41ecf Binary files /dev/null and b/documentation/html/AreWeHome/images/00049.png differ diff --git a/documentation/html/AreWeHome/images/00050.html b/documentation/html/AreWeHome/images/00050.html new file mode 100644 index 0000000..e494cd3 --- /dev/null +++ b/documentation/html/AreWeHome/images/00050.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00050.png b/documentation/html/AreWeHome/images/00050.png new file mode 100644 index 0000000..87b16c5 Binary files /dev/null and b/documentation/html/AreWeHome/images/00050.png differ diff --git a/documentation/html/AreWeHome/images/00051.html b/documentation/html/AreWeHome/images/00051.html new file mode 100644 index 0000000..58c2b07 --- /dev/null +++ b/documentation/html/AreWeHome/images/00051.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00051.png b/documentation/html/AreWeHome/images/00051.png new file mode 100644 index 0000000..3d4c3ed Binary files /dev/null and b/documentation/html/AreWeHome/images/00051.png differ diff --git a/documentation/html/AreWeHome/images/00052.html b/documentation/html/AreWeHome/images/00052.html new file mode 100644 index 0000000..618f48c --- /dev/null +++ b/documentation/html/AreWeHome/images/00052.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00052.png b/documentation/html/AreWeHome/images/00052.png new file mode 100644 index 0000000..57a9687 Binary files /dev/null and b/documentation/html/AreWeHome/images/00052.png differ diff --git a/documentation/html/AreWeHome/images/00053.html b/documentation/html/AreWeHome/images/00053.html new file mode 100644 index 0000000..58783de --- /dev/null +++ b/documentation/html/AreWeHome/images/00053.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00053.png b/documentation/html/AreWeHome/images/00053.png new file mode 100644 index 0000000..4331187 Binary files /dev/null and b/documentation/html/AreWeHome/images/00053.png differ diff --git a/documentation/html/AreWeHome/images/00054.html b/documentation/html/AreWeHome/images/00054.html new file mode 100644 index 0000000..16c2676 --- /dev/null +++ b/documentation/html/AreWeHome/images/00054.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00054.png b/documentation/html/AreWeHome/images/00054.png new file mode 100644 index 0000000..b3d4575 Binary files /dev/null and b/documentation/html/AreWeHome/images/00054.png differ diff --git a/documentation/html/AreWeHome/images/00055.html b/documentation/html/AreWeHome/images/00055.html new file mode 100644 index 0000000..3da7b0f --- /dev/null +++ b/documentation/html/AreWeHome/images/00055.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00055.png b/documentation/html/AreWeHome/images/00055.png new file mode 100644 index 0000000..d360570 Binary files /dev/null and b/documentation/html/AreWeHome/images/00055.png differ diff --git a/documentation/html/AreWeHome/images/00056.html b/documentation/html/AreWeHome/images/00056.html new file mode 100644 index 0000000..2e6fd0f --- /dev/null +++ b/documentation/html/AreWeHome/images/00056.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00056.png b/documentation/html/AreWeHome/images/00056.png new file mode 100644 index 0000000..5741fed Binary files /dev/null and b/documentation/html/AreWeHome/images/00056.png differ diff --git a/documentation/html/AreWeHome/images/00057.html b/documentation/html/AreWeHome/images/00057.html new file mode 100644 index 0000000..a910c40 --- /dev/null +++ b/documentation/html/AreWeHome/images/00057.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00057.png b/documentation/html/AreWeHome/images/00057.png new file mode 100644 index 0000000..e47bf81 Binary files /dev/null and b/documentation/html/AreWeHome/images/00057.png differ diff --git a/documentation/html/AreWeHome/images/00058.html b/documentation/html/AreWeHome/images/00058.html new file mode 100644 index 0000000..329a621 --- /dev/null +++ b/documentation/html/AreWeHome/images/00058.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00058.png b/documentation/html/AreWeHome/images/00058.png new file mode 100644 index 0000000..e7496d9 Binary files /dev/null and b/documentation/html/AreWeHome/images/00058.png differ diff --git a/documentation/html/AreWeHome/images/00059.html b/documentation/html/AreWeHome/images/00059.html new file mode 100644 index 0000000..d7f79e8 --- /dev/null +++ b/documentation/html/AreWeHome/images/00059.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00059.png b/documentation/html/AreWeHome/images/00059.png new file mode 100644 index 0000000..19d545f Binary files /dev/null and b/documentation/html/AreWeHome/images/00059.png differ diff --git a/documentation/html/AreWeHome/images/00060.html b/documentation/html/AreWeHome/images/00060.html new file mode 100644 index 0000000..b64c13e --- /dev/null +++ b/documentation/html/AreWeHome/images/00060.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00060.png b/documentation/html/AreWeHome/images/00060.png new file mode 100644 index 0000000..7a12b1c Binary files /dev/null and b/documentation/html/AreWeHome/images/00060.png differ diff --git a/documentation/html/AreWeHome/images/00061.html b/documentation/html/AreWeHome/images/00061.html new file mode 100644 index 0000000..cf9f032 --- /dev/null +++ b/documentation/html/AreWeHome/images/00061.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00061.png b/documentation/html/AreWeHome/images/00061.png new file mode 100644 index 0000000..7dec9ff Binary files /dev/null and b/documentation/html/AreWeHome/images/00061.png differ diff --git a/documentation/html/AreWeHome/images/00062.html b/documentation/html/AreWeHome/images/00062.html new file mode 100644 index 0000000..6021f9a --- /dev/null +++ b/documentation/html/AreWeHome/images/00062.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00062.png b/documentation/html/AreWeHome/images/00062.png new file mode 100644 index 0000000..fa66660 Binary files /dev/null and b/documentation/html/AreWeHome/images/00062.png differ diff --git a/documentation/html/AreWeHome/images/00063.html b/documentation/html/AreWeHome/images/00063.html new file mode 100644 index 0000000..b8becfc --- /dev/null +++ b/documentation/html/AreWeHome/images/00063.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00063.png b/documentation/html/AreWeHome/images/00063.png new file mode 100644 index 0000000..2b9ff31 Binary files /dev/null and b/documentation/html/AreWeHome/images/00063.png differ diff --git a/documentation/html/AreWeHome/images/00064.html b/documentation/html/AreWeHome/images/00064.html new file mode 100644 index 0000000..e3c94dd --- /dev/null +++ b/documentation/html/AreWeHome/images/00064.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00064.png b/documentation/html/AreWeHome/images/00064.png new file mode 100644 index 0000000..411f300 Binary files /dev/null and b/documentation/html/AreWeHome/images/00064.png differ diff --git a/documentation/html/AreWeHome/images/00065.html b/documentation/html/AreWeHome/images/00065.html new file mode 100644 index 0000000..333189b --- /dev/null +++ b/documentation/html/AreWeHome/images/00065.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00065.png b/documentation/html/AreWeHome/images/00065.png new file mode 100644 index 0000000..4454eef Binary files /dev/null and b/documentation/html/AreWeHome/images/00065.png differ diff --git a/documentation/html/AreWeHome/images/00066.html b/documentation/html/AreWeHome/images/00066.html new file mode 100644 index 0000000..d20a8c1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00066.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00066.png b/documentation/html/AreWeHome/images/00066.png new file mode 100644 index 0000000..b83b178 Binary files /dev/null and b/documentation/html/AreWeHome/images/00066.png differ diff --git a/documentation/html/AreWeHome/images/00067.html b/documentation/html/AreWeHome/images/00067.html new file mode 100644 index 0000000..faf46be --- /dev/null +++ b/documentation/html/AreWeHome/images/00067.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00067.png b/documentation/html/AreWeHome/images/00067.png new file mode 100644 index 0000000..de8f68d Binary files /dev/null and b/documentation/html/AreWeHome/images/00067.png differ diff --git a/documentation/html/AreWeHome/images/00068.html b/documentation/html/AreWeHome/images/00068.html new file mode 100644 index 0000000..b5cfa08 --- /dev/null +++ b/documentation/html/AreWeHome/images/00068.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00068.png b/documentation/html/AreWeHome/images/00068.png new file mode 100644 index 0000000..78dde0b Binary files /dev/null and b/documentation/html/AreWeHome/images/00068.png differ diff --git a/documentation/html/AreWeHome/images/00069.html b/documentation/html/AreWeHome/images/00069.html new file mode 100644 index 0000000..444c3a3 --- /dev/null +++ b/documentation/html/AreWeHome/images/00069.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00069.png b/documentation/html/AreWeHome/images/00069.png new file mode 100644 index 0000000..5dd5c97 Binary files /dev/null and b/documentation/html/AreWeHome/images/00069.png differ diff --git a/documentation/html/AreWeHome/images/00070.html b/documentation/html/AreWeHome/images/00070.html new file mode 100644 index 0000000..558ef69 --- /dev/null +++ b/documentation/html/AreWeHome/images/00070.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00070.png b/documentation/html/AreWeHome/images/00070.png new file mode 100644 index 0000000..0c61a1f Binary files /dev/null and b/documentation/html/AreWeHome/images/00070.png differ diff --git a/documentation/html/AreWeHome/images/00071.html b/documentation/html/AreWeHome/images/00071.html new file mode 100644 index 0000000..9d6737e --- /dev/null +++ b/documentation/html/AreWeHome/images/00071.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00071.png b/documentation/html/AreWeHome/images/00071.png new file mode 100644 index 0000000..377932a Binary files /dev/null and b/documentation/html/AreWeHome/images/00071.png differ diff --git a/documentation/html/AreWeHome/images/00072.html b/documentation/html/AreWeHome/images/00072.html new file mode 100644 index 0000000..b249ffa --- /dev/null +++ b/documentation/html/AreWeHome/images/00072.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00072.png b/documentation/html/AreWeHome/images/00072.png new file mode 100644 index 0000000..01108b4 Binary files /dev/null and b/documentation/html/AreWeHome/images/00072.png differ diff --git a/documentation/html/AreWeHome/images/00073.html b/documentation/html/AreWeHome/images/00073.html new file mode 100644 index 0000000..e2a3859 --- /dev/null +++ b/documentation/html/AreWeHome/images/00073.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00073.png b/documentation/html/AreWeHome/images/00073.png new file mode 100644 index 0000000..5dee15b Binary files /dev/null and b/documentation/html/AreWeHome/images/00073.png differ diff --git a/documentation/html/AreWeHome/images/00074.html b/documentation/html/AreWeHome/images/00074.html new file mode 100644 index 0000000..c98c950 --- /dev/null +++ b/documentation/html/AreWeHome/images/00074.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00074.png b/documentation/html/AreWeHome/images/00074.png new file mode 100644 index 0000000..16348d1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00074.png differ diff --git a/documentation/html/AreWeHome/images/00075.html b/documentation/html/AreWeHome/images/00075.html new file mode 100644 index 0000000..9287d13 --- /dev/null +++ b/documentation/html/AreWeHome/images/00075.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00075.png b/documentation/html/AreWeHome/images/00075.png new file mode 100644 index 0000000..72d2363 Binary files /dev/null and b/documentation/html/AreWeHome/images/00075.png differ diff --git a/documentation/html/AreWeHome/images/00076.html b/documentation/html/AreWeHome/images/00076.html new file mode 100644 index 0000000..20f4530 --- /dev/null +++ b/documentation/html/AreWeHome/images/00076.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00076.png b/documentation/html/AreWeHome/images/00076.png new file mode 100644 index 0000000..1862909 Binary files /dev/null and b/documentation/html/AreWeHome/images/00076.png differ diff --git a/documentation/html/AreWeHome/images/00077.html b/documentation/html/AreWeHome/images/00077.html new file mode 100644 index 0000000..63fefe7 --- /dev/null +++ b/documentation/html/AreWeHome/images/00077.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00077.png b/documentation/html/AreWeHome/images/00077.png new file mode 100644 index 0000000..954cc18 Binary files /dev/null and b/documentation/html/AreWeHome/images/00077.png differ diff --git a/documentation/html/AreWeHome/images/00078.html b/documentation/html/AreWeHome/images/00078.html new file mode 100644 index 0000000..c5e10e3 --- /dev/null +++ b/documentation/html/AreWeHome/images/00078.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00078.png b/documentation/html/AreWeHome/images/00078.png new file mode 100644 index 0000000..f4c576a Binary files /dev/null and b/documentation/html/AreWeHome/images/00078.png differ diff --git a/documentation/html/AreWeHome/images/00079.html b/documentation/html/AreWeHome/images/00079.html new file mode 100644 index 0000000..3c40750 --- /dev/null +++ b/documentation/html/AreWeHome/images/00079.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00079.png b/documentation/html/AreWeHome/images/00079.png new file mode 100644 index 0000000..596fa0e Binary files /dev/null and b/documentation/html/AreWeHome/images/00079.png differ diff --git a/documentation/html/AreWeHome/images/00080.html b/documentation/html/AreWeHome/images/00080.html new file mode 100644 index 0000000..96126a0 --- /dev/null +++ b/documentation/html/AreWeHome/images/00080.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00080.png b/documentation/html/AreWeHome/images/00080.png new file mode 100644 index 0000000..5dbedc6 Binary files /dev/null and b/documentation/html/AreWeHome/images/00080.png differ diff --git a/documentation/html/AreWeHome/images/00081.html b/documentation/html/AreWeHome/images/00081.html new file mode 100644 index 0000000..a26d743 --- /dev/null +++ b/documentation/html/AreWeHome/images/00081.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00081.png b/documentation/html/AreWeHome/images/00081.png new file mode 100644 index 0000000..2759269 Binary files /dev/null and b/documentation/html/AreWeHome/images/00081.png differ diff --git a/documentation/html/AreWeHome/images/00082.html b/documentation/html/AreWeHome/images/00082.html new file mode 100644 index 0000000..3da1500 --- /dev/null +++ b/documentation/html/AreWeHome/images/00082.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00082.png b/documentation/html/AreWeHome/images/00082.png new file mode 100644 index 0000000..d91e5d9 Binary files /dev/null and b/documentation/html/AreWeHome/images/00082.png differ diff --git a/documentation/html/AreWeHome/images/00083.html b/documentation/html/AreWeHome/images/00083.html new file mode 100644 index 0000000..a36069e --- /dev/null +++ b/documentation/html/AreWeHome/images/00083.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00083.png b/documentation/html/AreWeHome/images/00083.png new file mode 100644 index 0000000..10fc21c Binary files /dev/null and b/documentation/html/AreWeHome/images/00083.png differ diff --git a/documentation/html/AreWeHome/images/00084.html b/documentation/html/AreWeHome/images/00084.html new file mode 100644 index 0000000..4176f4f --- /dev/null +++ b/documentation/html/AreWeHome/images/00084.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00084.png b/documentation/html/AreWeHome/images/00084.png new file mode 100644 index 0000000..5f281b1 Binary files /dev/null and b/documentation/html/AreWeHome/images/00084.png differ diff --git a/documentation/html/AreWeHome/images/00085.html b/documentation/html/AreWeHome/images/00085.html new file mode 100644 index 0000000..16f80a1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00085.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00085.png b/documentation/html/AreWeHome/images/00085.png new file mode 100644 index 0000000..7981681 Binary files /dev/null and b/documentation/html/AreWeHome/images/00085.png differ diff --git a/documentation/html/AreWeHome/images/00086.html b/documentation/html/AreWeHome/images/00086.html new file mode 100644 index 0000000..ceb7427 --- /dev/null +++ b/documentation/html/AreWeHome/images/00086.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00086.png b/documentation/html/AreWeHome/images/00086.png new file mode 100644 index 0000000..93f6eff Binary files /dev/null and b/documentation/html/AreWeHome/images/00086.png differ diff --git a/documentation/html/AreWeHome/images/00087.html b/documentation/html/AreWeHome/images/00087.html new file mode 100644 index 0000000..bfc1483 --- /dev/null +++ b/documentation/html/AreWeHome/images/00087.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00087.png b/documentation/html/AreWeHome/images/00087.png new file mode 100644 index 0000000..6c40fa9 Binary files /dev/null and b/documentation/html/AreWeHome/images/00087.png differ diff --git a/documentation/html/AreWeHome/images/00088.html b/documentation/html/AreWeHome/images/00088.html new file mode 100644 index 0000000..fb6d8b1 --- /dev/null +++ b/documentation/html/AreWeHome/images/00088.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00088.png b/documentation/html/AreWeHome/images/00088.png new file mode 100644 index 0000000..b413d6e Binary files /dev/null and b/documentation/html/AreWeHome/images/00088.png differ diff --git a/documentation/html/AreWeHome/images/00089.html b/documentation/html/AreWeHome/images/00089.html new file mode 100644 index 0000000..e467ace --- /dev/null +++ b/documentation/html/AreWeHome/images/00089.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00089.png b/documentation/html/AreWeHome/images/00089.png new file mode 100644 index 0000000..642bfb0 Binary files /dev/null and b/documentation/html/AreWeHome/images/00089.png differ diff --git a/documentation/html/AreWeHome/images/00090.html b/documentation/html/AreWeHome/images/00090.html new file mode 100644 index 0000000..bbf70bf --- /dev/null +++ b/documentation/html/AreWeHome/images/00090.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00090.png b/documentation/html/AreWeHome/images/00090.png new file mode 100644 index 0000000..c3ea4d2 Binary files /dev/null and b/documentation/html/AreWeHome/images/00090.png differ diff --git a/documentation/html/AreWeHome/images/00091.html b/documentation/html/AreWeHome/images/00091.html new file mode 100644 index 0000000..f5d4592 --- /dev/null +++ b/documentation/html/AreWeHome/images/00091.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00091.png b/documentation/html/AreWeHome/images/00091.png new file mode 100644 index 0000000..d778f64 Binary files /dev/null and b/documentation/html/AreWeHome/images/00091.png differ diff --git a/documentation/html/AreWeHome/images/00092.html b/documentation/html/AreWeHome/images/00092.html new file mode 100644 index 0000000..34d7b15 --- /dev/null +++ b/documentation/html/AreWeHome/images/00092.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00092.png b/documentation/html/AreWeHome/images/00092.png new file mode 100644 index 0000000..6ab80ac Binary files /dev/null and b/documentation/html/AreWeHome/images/00092.png differ diff --git a/documentation/html/AreWeHome/images/00093.html b/documentation/html/AreWeHome/images/00093.html new file mode 100644 index 0000000..0c5456f --- /dev/null +++ b/documentation/html/AreWeHome/images/00093.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00093.png b/documentation/html/AreWeHome/images/00093.png new file mode 100644 index 0000000..5ef3582 Binary files /dev/null and b/documentation/html/AreWeHome/images/00093.png differ diff --git a/documentation/html/AreWeHome/images/00094.html b/documentation/html/AreWeHome/images/00094.html new file mode 100644 index 0000000..8abb1be --- /dev/null +++ b/documentation/html/AreWeHome/images/00094.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00094.png b/documentation/html/AreWeHome/images/00094.png new file mode 100644 index 0000000..c71cc4c Binary files /dev/null and b/documentation/html/AreWeHome/images/00094.png differ diff --git a/documentation/html/AreWeHome/images/00095.html b/documentation/html/AreWeHome/images/00095.html new file mode 100644 index 0000000..86175c2 --- /dev/null +++ b/documentation/html/AreWeHome/images/00095.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00095.png b/documentation/html/AreWeHome/images/00095.png new file mode 100644 index 0000000..9ba0472 Binary files /dev/null and b/documentation/html/AreWeHome/images/00095.png differ diff --git a/documentation/html/AreWeHome/images/00096.html b/documentation/html/AreWeHome/images/00096.html new file mode 100644 index 0000000..e2e486f --- /dev/null +++ b/documentation/html/AreWeHome/images/00096.html @@ -0,0 +1,46 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00096.png b/documentation/html/AreWeHome/images/00096.png new file mode 100644 index 0000000..bd3dbd9 Binary files /dev/null and b/documentation/html/AreWeHome/images/00096.png differ diff --git a/documentation/html/AreWeHome/images/00097.html b/documentation/html/AreWeHome/images/00097.html new file mode 100644 index 0000000..b48a69a --- /dev/null +++ b/documentation/html/AreWeHome/images/00097.html @@ -0,0 +1,43 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + + + +
+ + + + +
+ + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/images/00097.png b/documentation/html/AreWeHome/images/00097.png new file mode 100644 index 0000000..6d08de7 Binary files /dev/null and b/documentation/html/AreWeHome/images/00097.png differ diff --git a/documentation/html/AreWeHome/index.html.bak b/documentation/html/AreWeHome/index.html.bak new file mode 100644 index 0000000..62d2eb4 --- /dev/null +++ b/documentation/html/AreWeHome/index.html.bak @@ -0,0 +1,41 @@ + + + +Sleep Is Death Flip Book + + + + + +
+
+ + + + + + + + + + + + +
+ + +
+ + + +
+ + + + +
+ + + + \ No newline at end of file diff --git a/documentation/html/AreWeHome/index.php b/documentation/html/AreWeHome/index.php new file mode 100644 index 0000000..2c5f4ce --- /dev/null +++ b/documentation/html/AreWeHome/index.php @@ -0,0 +1,138 @@ +"; + +// clicking on image goes to next +if( $next != -1 ) { + echo ""; + } +echo ""; +if( $next != -1 ) { + echo ""; + } + +echo ""; + + +echo ""; + +echo ""; +if( $prev != -1 ) { + echo "". + ""; + } +else { + echo ""; + } +echo ""; + +echo ""; + +echo ""; + +echo ""; + + +echo ""; +if( $next != -1 ) { + echo "". + ""; + } +else { + echo ""; + } +echo ""; + +echo ""; + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + +if( $next != -1 ) { + echo ""; + } + + + +include( "footer.php" ); + +?> \ No newline at end of file diff --git a/documentation/html/AreWeHome/next.png b/documentation/html/AreWeHome/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/AreWeHome/next.png differ diff --git a/documentation/html/AreWeHome/noArrow.png b/documentation/html/AreWeHome/noArrow.png new file mode 100644 index 0000000..3e7a163 Binary files /dev/null and b/documentation/html/AreWeHome/noArrow.png differ diff --git a/documentation/html/AreWeHome/prev.png b/documentation/html/AreWeHome/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/documentation/html/AreWeHome/prev.png differ diff --git a/documentation/html/amazingStories.php b/documentation/html/amazingStories.php new file mode 100644 index 0000000..a17bfa2 --- /dev/null +++ b/documentation/html/amazingStories.php @@ -0,0 +1,177 @@ +"; + + if( $days > 1 ) { + echo "$days days"; + } + else { + $hours += 24 * $days; + + if( $hours > 1 ) { + echo "$hours hours"; + } + else { + $minutes += 60 * $hours; + + if( $minutes > 1 ) { + echo "$minutes minutes"; + } + else { + $seconds += 60 * $minutes; + + echo "$seconds seconds"; + } + } + } + echo ""; + } + +?> + +I'm looking for some Amazing stories...
+
+ + + +...and I'm willing to pay you for them.
+
+I'm looking for the best examples of what can be done with Sleep Is Death. If you're really pushing at the boundaries, discovering fresh techniques, and helping to reinvent how this tool can be used, please submit an example of your work to this challenge. + + +
+
+
+
+ +The challenge

+By my definition, an amazing story has these properties: +
    +
  • Uses all new, custom-designed resources. Does not cobble together scenes out of the pre-made resources or slightly modified pre-made resources. +
  • Has a consistent and unique visual style. +
  • Breaks away from scene-layout conventions in some way. (Are people always two-tiles tall? Is the view always top-down? What else can we do with this toolset?) +
  • Has good synergy between Player and Controller. +
  • Has well-developed, well-acted characters from both players (no OMG or LOLZ, k?). +
  • Has an interesting, engaging story arc that incorporates the Player's decisions and push-back. +
  • Is not overly long (120 turns max). +
+Got a story like this? Have it saved as a flip-book? Great! + + + + +
+
+
+
+ +The offer

+I'm looking for up to five of the very best stories to feature here on the main site. If your story is chosen, I will send you $200 US through PayPal.
+
+Everyone who submits a story that meets the criteria (in my generous opinion--and I don't have time to argue with anyone, so please don't try) will receive a free extra download code that you can pass on to a friend for the April 16th release. + + +
+
+
+
+ +The submission

+Your submission must include: +
    +
  1. A ZIP'ed copy of your story's flip-book folder. +
  2. Flip- book images MUST be the standard 640x416 in size (if your Player has changed her screen size from the default, please have her change it back to 640x480 so as not to invoke the v13 flip-book sizing glitch). +
  3. A resource pack (.pak file) containing all the new scenes and objects that you used in the story. +
  4. A title. +
  5. The name (or handle) of both the Controller and the Player who participated in your story (for credit). +
  6. Your PayPal email address. +
+Send all this, via email (with attachments) to:

+
sleepisdeath77 (AT) gmail.com
+ + + + +
+
+
+ +The deadline

+(Extended!)
+ +Your submission must be received at the above email address by Midnight, New York Time, on the night of Mondy, April 20, 2010.

+
Yes, that's + 0 ) { + + showTimeLeft( $timeLeft ); + } +else { + echo "already passed (too late!)"; + } +?> +.
+
+ +Results will be posted on the main site along with the v14 release on Tuesday, April 20, 2010, at 8pm New York time. If your story has been chosen, you will be paid by PayPal before the results are posted. + + +
+
+
+
+ +The fine print

+ +I'm the judge and the only judge. All my decisions are final. This is a contest of skill, not a lottery. There is no entry fee. If such contests are illegal in your region, please don't submit anything.
+
+If the unthinkable should happen, and I get hundreds of entries, I will try to look at them all, but I reserve the right to only look at the first 100 that are received.
+
+Your flip-book will be shared with the world through this website. Your resource pack, however, will only be downloadable from this website by people who have a valid download code for the game. You retain ownership over your resource pack, and you can distribute it on your own as you see fit. If we must invoke copyright talk: if your submission is chosen, you retain ownership over it, but you grant me a non-exclusive forever-license to modify and distribute your submitted material in conjunction with any and all promotion of Sleep Is Death. + +
+
+
+ + diff --git a/documentation/html/buster.sk b/documentation/html/buster.sk new file mode 100644 index 0000000..e304888 --- /dev/null +++ b/documentation/html/buster.sk @@ -0,0 +1,14 @@ +##Sketch 1 2 +document() +layout('A4',0) +layer('Layer 1',1,1,0,0,(0,0,0)) +lp((1,0,0)) +lw(15) +e(57.1002,0,0,-57.1002,247.002,544.842) +lp((1,0,0)) +lw(15) +b() +bs(281.477,507.134,0) +bs(212.526,582.549,0) +guidelayer('Guide Lines',1,0,0,1,(0,0,1)) +grid((0,0,20,20),0,(0,0,1),'Grid') diff --git a/documentation/html/community.php b/documentation/html/community.php new file mode 100644 index 0000000..01b7625 --- /dev/null +++ b/documentation/html/community.php @@ -0,0 +1,35 @@ + + +Community Links
+
+ + +Sleep Is Death Stories -- A growing collection. Post your own. Also has forums. + + +
+
+ +SIDTube -- Game matchmaking, resource pack sharing, story gallery, forums, wiki, IRC (chat). + +
+
+ +Ishani's Sleep Is Death Page -- A tool for importing tile sets and objects. +
+
+ +Flipped -- A tool for editing and merging flip books. + +
+
+ +Sleepy -- A cross-platform tool for managing resource caches and resource packs. + + + +
+
+
+ + diff --git a/documentation/html/compileNotes.php b/documentation/html/compileNotes.php new file mode 100644 index 0000000..fe646c3 --- /dev/null +++ b/documentation/html/compileNotes.php @@ -0,0 +1,17 @@ + + +Compiling from source
+
+Make sure you have dev packages of the following libraries installed: +
    +
  • libsdl +
  • libpng +
  • zlib +
+The included "runToBuild" script should build the game for you automatically. + +
+
+
+ + diff --git a/documentation/html/crossPlatform.png b/documentation/html/crossPlatform.png new file mode 100644 index 0000000..4bbe879 Binary files /dev/null and b/documentation/html/crossPlatform.png differ diff --git a/documentation/html/footer.php b/documentation/html/footer.php new file mode 100644 index 0000000..7e859f1 --- /dev/null +++ b/documentation/html/footer.php @@ -0,0 +1,40 @@ + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/documentation/html/fs_button05.gif b/documentation/html/fs_button05.gif new file mode 100644 index 0000000..6801f1c Binary files /dev/null and b/documentation/html/fs_button05.gif differ diff --git a/documentation/html/fs_button05.png b/documentation/html/fs_button05.png new file mode 100644 index 0000000..40f7186 Binary files /dev/null and b/documentation/html/fs_button05.png differ diff --git a/documentation/html/fs_button15.gif b/documentation/html/fs_button15.gif new file mode 100644 index 0000000..bd27b93 Binary files /dev/null and b/documentation/html/fs_button15.gif differ diff --git a/documentation/html/fs_button15.png b/documentation/html/fs_button15.png new file mode 100644 index 0000000..d7b3d06 Binary files /dev/null and b/documentation/html/fs_button15.png differ diff --git a/documentation/html/fs_cards.png b/documentation/html/fs_cards.png new file mode 100644 index 0000000..ceb0743 Binary files /dev/null and b/documentation/html/fs_cards.png differ diff --git a/documentation/html/galvin.php b/documentation/html/galvin.php new file mode 100644 index 0000000..5a61c38 --- /dev/null +++ b/documentation/html/galvin.php @@ -0,0 +1,64 @@ + + +
+ +
+ +
+
+ +Shannon Galvin's Contributions
+
+Shannon Galvin worked at Maxis starting back in 1994 and all +the way up through Spore, where he did concept art and 3D modeling (remember +the plants and the huts---that was him!). He's currently +working as lead 3D artist on Jonathan Blow's upcoming game The Witness. +
+
+He made a full set of resources that he used to tell me a pretty great story, which you can flip through here: +
+Shannon vs. Jason: Are We Home? +
+
+
+He also packed all of this stuff up into a resource pack, which is available for free to everyone who bought download access to the game. You can use his stuff to tell your own stories, or just poke at it to figure out how he made all those subtle shading effects. +
+
+ +You can see more of his personal work here: http://silver-rin.blogspot.com
+
+And there's more! Two tutorials: + +
+
+
+ +
+ + + + +
+ + + +
+
+ +
+
+ +
+ + + + +
+ + + +
+
+ + + diff --git a/documentation/html/galvin1.png b/documentation/html/galvin1.png new file mode 100644 index 0000000..cf10243 Binary files /dev/null and b/documentation/html/galvin1.png differ diff --git a/documentation/html/galvin2.png b/documentation/html/galvin2.png new file mode 100644 index 0000000..479280b Binary files /dev/null and b/documentation/html/galvin2.png differ diff --git a/documentation/html/galvinTutorial1.png b/documentation/html/galvinTutorial1.png new file mode 100644 index 0000000..38499c0 Binary files /dev/null and b/documentation/html/galvinTutorial1.png differ diff --git a/documentation/html/galvinTutorial2.png b/documentation/html/galvinTutorial2.png new file mode 100644 index 0000000..14457b0 Binary files /dev/null and b/documentation/html/galvinTutorial2.png differ diff --git a/documentation/html/header.php b/documentation/html/header.php new file mode 100644 index 0000000..83f5af6 --- /dev/null +++ b/documentation/html/header.php @@ -0,0 +1,17 @@ + + + +Sleep Is Death (Geisterfahrer) + + + + +
+
+ +", + // opening tags for each text block + "" . + "" . + "", + // story separator + "


" ); +*/ +?> + +
+ + + + +
+ + + + + + +
+ +
+ + diff --git a/documentation/html/header.png b/documentation/html/header.png new file mode 100644 index 0000000..c20cac4 Binary files /dev/null and b/documentation/html/header.png differ diff --git a/documentation/html/index.php b/documentation/html/index.php new file mode 100644 index 0000000..8d1304f --- /dev/null +++ b/documentation/html/index.php @@ -0,0 +1,478 @@ + + + + + + + + Access your download:
+
+ + Enter Code: + + +
+ + +
+ + Buy downloads
+ "; + + echo ""; + } +?> +
+
+ +
+
    +
  • Unlimited downloads for two people +
  • Access to all future updates +
  • Tech support included +
  • Support me and my family directly
    (so I can make more games) +
+
+ What's it worth to you?

+
+ +
+ Name your price: $
+ +
+ +
+
+ +
+
    +
  • Unlimited downloads for two people +
  • Access to all future updates +
  • Tech support included +
  • Support me and my family directly
    (so I can make more games) +
+
+ Available now for $14

+
+ +
+
+ +
+"; + + if( $days > 1 ) { + echo "$days days"; + } + else { + $hours += 24 * $days; + + if( $hours > 1 ) { + echo "$hours hours"; + } + else { + $minutes += 60 * $hours; + + if( $minutes > 1 ) { + echo "$minutes minutes"; + } + else { + $seconds += 60 * $minutes; + + echo "$seconds seconds"; + } + } + } + echo " left to pre-order"; + } + + + +function showLogo( $inImageFile, $inText ) { + + echo "
+
+ $inText +
"; + } + + +?> + + +
+Sleep Is Death (Geisterfahrer)
+a storytelling game for two players by +Jason Rohrer
+
+ +[news] -- +[videos] -- +[community] -- +[jason's stories] -- +[other stories] +
+ + + +
+ + + + + + + + + +
+90/100
+Editor's Choice
+PC Gamer UK
+June 2010 +
+5/5
+Excellent
+GamerNode +
+ +
+ + +"I had stared right into the eye of the future,
+and was left with nothing to do about it but
+wait for the rest of the industry to catch up."

+--Justin McElroy, Joystiq Preview

+ + + +"We already have movies, yes. +We already even have plenty of video games.
+We've never had anything like this."
+--Leigh Alexander, Kotaku Preview

+ + +"No other videogame has offered me so much."
+--Anthony Burch, Destructoid Previews [1] [2]

+ + + +"In this blocky technical artifact, I discovered something
+alarmingly dark, personal, and beautiful.
+And we had made it together."

--Michael Thomsen, IGN.com Preview

+ + + +"Something that no game has done to me before"
--Brandon Boyer, In-depth preview on Boing Boing

+ +"Incredibly awesome" --Richard Lemarchand (Lead Designer, Uncharted 2) +
+
+
+ +
+ + + + + + + + +
+ +
+ + + + + +Two for One
+ +
+When you buy the game, you're buying it for two people. You can share your download link with a friend, as a gift. +
+
+This is v16, the official public release. + + + + + +
+ +
+ + + + + +
+
+ + +
+
+
+
+ + 0 ) { + + showTimeLeft( $timeLeft ); + } +else { + echo "Pre-Orders are now closed"; + } +*/ +?> + +
+ + 0 ) { + echo "
"; + // only credit card button + showPayLinks( false ); + } +else { + //echo "
"; + //echo "
Downloads will be availble soon.
"; + + showDownloadForm(); + + echo "
Orders for April 16 are open
"; + + echo "
"; + // only credit card button + showPayLinks( false ); + } +*/ +?> +
+
+
+
+
+ + +What you get
+ +
+ +Immediately after your payment is processed, you will receive an email with an access link for two people (the game is two-player only). You and a friend will then be able to download all of the following DRM-free distributions: +
+
+
    +
  • Windows build
  • +
  • MacOS build (10.2 and later, PPC/Intel)
  • +
  • Full source code bundle (which can also be compiled on GNU/Linux)
  • +
  • Several resource packs, including one by Spore artist Shannon Galvin
  • +
+
+
+The price also includes downloads of all future updates.
+
+You can take a look at the system requirements. +
+
+
+ +
+
+
+
+ +
+
+
+
+ +Credits
+ +
+This game was commissioned as part of the Art History of Games Conference, which was co-sponsored by Georgia Tech and SCAD.
+
+Development was also made possible by the support of Jeff Roberts. +
+
+All design, programming, graphics, fonts, and sound by Jason Rohrer. + +The graphics were made with mtPaint. The SDL library provides cross-platform screen, sound, and user input. libpng and zlib enable PNG output. MinGW was used to build the game for Windows. + +
+
+
+ +
+ +
+
+
+ + + + diff --git a/documentation/html/myStories.php b/documentation/html/myStories.php new file mode 100644 index 0000000..53e05b9 --- /dev/null +++ b/documentation/html/myStories.php @@ -0,0 +1,93 @@ + + +Jason's Stories +
+I told the following stories while demonstrating Sleep Is Death to various friends and journalists. +
+
+
+ + +
+
+Good Heart, May 4, 2010 (played with Chris McMahon) +
+ +
+
+
+
+ + +
+
+Mister Fur, April 30, 2010 (played with Randy Smith) +
+ +
+
+
+
+ + +
+
+Water, April 23, 2010 (played with Stuart Horvath) +
+ +
+
+
+
+ + +
+
+Water, April 2, 2010 (played with Justin McElroy) +
+ +
+
+
+
+ + + +
+
+Meeting Travis, April 1, 2010 (played with Mike Rose) +
+ +
+
+
+
+ + +
+
+Police Work, March 30, 2010 (played with Anthony Burch) +
+ +
+
+
+
+ + +
+
+Waiting, March 26, 2010 (played with Leigh Alexander) +
+ +
+
+
+
+ + + + + + + diff --git a/documentation/html/nameYourPrice.php b/documentation/html/nameYourPrice.php new file mode 100644 index 0000000..ab72458 --- /dev/null +++ b/documentation/html/nameYourPrice.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/documentation/html/news.php b/documentation/html/news.php new file mode 100644 index 0000000..3ff2c91 --- /dev/null +++ b/documentation/html/news.php @@ -0,0 +1,125 @@ + + + + + + + + + + + + +
+ + +
RSS 2.0
+ +
+Alternative: @sleepisdeath77 on Twitter + + + + + +
+ + +", + // closing tags for each story block + "
", + // opening tags for headlines + "
" . + "", + // closing tags for headlines + "
" . + "
", + // closing tags for each text block + "
+ + + +
+[login] +
+ +
+ + + diff --git a/documentation/html/newsLogin.php b/documentation/html/newsLogin.php new file mode 100644 index 0000000..ad779ab --- /dev/null +++ b/documentation/html/newsLogin.php @@ -0,0 +1,19 @@ + + +[news main]
+ +
+
+
+ + +
+ + + + \ No newline at end of file diff --git a/documentation/html/next.png b/documentation/html/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/next.png differ diff --git a/documentation/html/noArrow.png b/documentation/html/noArrow.png new file mode 100644 index 0000000..3e7a163 Binary files /dev/null and b/documentation/html/noArrow.png differ diff --git a/documentation/html/noBotsHeader.php b/documentation/html/noBotsHeader.php new file mode 100644 index 0000000..67e7870 --- /dev/null +++ b/documentation/html/noBotsHeader.php @@ -0,0 +1,21 @@ + + + +Sleep Is Death (Geisterfahrer) + + + + + + + + +
+
+ + + +
+ + + +
+ + diff --git a/documentation/html/noCounterFooter.php b/documentation/html/noCounterFooter.php new file mode 100644 index 0000000..7653f38 --- /dev/null +++ b/documentation/html/noCounterFooter.php @@ -0,0 +1,17 @@ + + +
+ +
+
+ + +
+ + + + diff --git a/documentation/html/noDRM.png b/documentation/html/noDRM.png new file mode 100644 index 0000000..f94e51c Binary files /dev/null and b/documentation/html/noDRM.png differ diff --git a/documentation/html/noTie.png b/documentation/html/noTie.png new file mode 100644 index 0000000..7643845 Binary files /dev/null and b/documentation/html/noTie.png differ diff --git a/documentation/html/openSource.png b/documentation/html/openSource.png new file mode 100644 index 0000000..6d98426 Binary files /dev/null and b/documentation/html/openSource.png differ diff --git a/documentation/html/orderPicture.png b/documentation/html/orderPicture.png new file mode 100644 index 0000000..559c846 Binary files /dev/null and b/documentation/html/orderPicture.png differ diff --git a/documentation/html/orderPicture2.png b/documentation/html/orderPicture2.png new file mode 100644 index 0000000..1fc8359 Binary files /dev/null and b/documentation/html/orderPicture2.png differ diff --git a/documentation/html/orderPicture3.png b/documentation/html/orderPicture3.png new file mode 100644 index 0000000..4fd8d94 Binary files /dev/null and b/documentation/html/orderPicture3.png differ diff --git a/documentation/html/orderPicture7.png b/documentation/html/orderPicture7.png new file mode 100644 index 0000000..ee4e7e9 Binary files /dev/null and b/documentation/html/orderPicture7.png differ diff --git a/documentation/html/orderPicture_nameYourPrice.png b/documentation/html/orderPicture_nameYourPrice.png new file mode 100644 index 0000000..431e133 Binary files /dev/null and b/documentation/html/orderPicture_nameYourPrice.png differ diff --git a/documentation/html/pressDemo/index.php b/documentation/html/pressDemo/index.php new file mode 100644 index 0000000..cc9c859 --- /dev/null +++ b/documentation/html/pressDemo/index.php @@ -0,0 +1,51 @@ + + +Press Demos
+ +
+ +You will need to obtain a demo code before you can run these. Please contact me. +
+
+
+ + + +
SleepIsDeath_v9bpress_Windows.exe
SleepIsDeath_v9press_MacOSX.dmg
+ +
+
+ + + + + + +
+
+
+ +
+
+
+ + + + + diff --git a/documentation/html/pressDemo/youTube.png b/documentation/html/pressDemo/youTube.png new file mode 100644 index 0000000..099e468 Binary files /dev/null and b/documentation/html/pressDemo/youTube.png differ diff --git a/documentation/html/requirements.php b/documentation/html/requirements.php new file mode 100644 index 0000000..0844fbf --- /dev/null +++ b/documentation/html/requirements.php @@ -0,0 +1,25 @@ + + +System Requirements
+
+The game was developed on a 900MHz PC, but it has been tested as marginally-playable on a 233MHz iMac. In other words, it will most likely run on your computer.
+
+The only required library is OpenGL, which ships standard on MacOS and Windows and is readily available on GNU/Linux as well.
+
+Sleep Is Death is two-player only.
+
+There is no single-player mode. It is easiest to play over a local network. Have a friend over and plug in two laptops, or play over local WiFi. It can be played remotely over the Internet, but you will need to send the other player your Internet address. Sleep Is Death works across most modern routers and firewalls. Instructions for remote Internet play are included. +
+
+Other stuff you will need: +
    +
  • keyboard
  • +
  • mouse
  • +
  • speakers or headphones
  • +
+If you are playing in the same room as your friend, it's best if at least one (or both) players use headphones. +
+
+
+ + diff --git a/documentation/html/sbSettings.php b/documentation/html/sbSettings.php new file mode 100644 index 0000000..faa8832 --- /dev/null +++ b/documentation/html/sbSettings.php @@ -0,0 +1,157 @@ +"; + + +// This is not displayed anywhere on the site, but instead is +// used when securely hashing passwords (so that the same password, if +// used on two different sites with different $siteShortName settings, +// will have different hashes in the database). +$siteShortName = "sidNews"; + + +// For standard displays (lists of posts), you call seedBlogs from +// within your web page, so the surrounding HTML is under your full control. +// However, some pages (such as the edit forms and the full story displays) +// are generated by seedBlogs in response to user actions. To customize +// the layout of these pages, you must provide header and footer files. + +// inserted at the top of every page generated by seedBlogs +$header = "header.php"; + +// inserted at the end of every page generated by seedBlogs +$footer = "footer.php"; + + + +// These are used to format stories in both list displays and full-story +// displays. +// The default settings are specified below, but they can be overridden +// for a given list display if you call the "advanced" seedBlog interface +// function (seedBlogFormatted). +// However, these default settings will always be used in full-story displays, +// archive list displays, and comment list displays. + +// HTML used to open/close each story block (only used in story lists that +// display intro text for each story) +$storyBlockFormatOpen = "
"; +$storyBlockFormatClose = "
"; + + +// HTML used to format headlines when intro text is shown +$headlineFormatOpen = ""; +$headlineFormatClose = ""; + +// HTML used to open/close the text below each headline +// this block of text includes: +// admin widgets, intro text, Read more link, and comment widget +$textBlockFormatOpen = "
"; +$textBlockFormatClose = "
"; + + +// HTML used to open/close each story block (for lists where intro text +// is hidden) +$linkStoryBlockFormatOpen = +"
--"; +$linkStoryBlockFormatClose = "
"; + + +// HTML used to format link-only headlines (for lists where intro text +// is hidden). These format strings are inserted *inside* the HTML link tags. +// Thus, they can be used to change the color of the link text. +$linkHeadlineFormatOpen = ""; +$linkHeadlineFormatClose = ""; + + +// HTML to insert between each story in a story list when intro text is shown +$storySeparator = "


"; + +// HTML to insert between each headline link when intro text is hidden +$linkStorySeparator = ""; + +// used around the list of comments (shown at the bottom of the story display) +// comment lists are formatted as a seedBlog using the default formatting +// settings specified above +$commentListOpen = "

Comments
"; +$commentListClose = ""; + + + +// End of all settings + + +?> \ No newline at end of file diff --git a/documentation/html/seedBlogs.php b/documentation/html/seedBlogs.php new file mode 100644 index 0000000..f1eef39 --- /dev/null +++ b/documentation/html/seedBlogs.php @@ -0,0 +1,4820 @@ + +seedBlogs Web-based setup + + +
+ +
+ +
"; + +$setup_footer = " +
+
+
+"; + + + + +// set to 1 to force magic_quote behavior on all user-submitted data +// set to 0 to disable magic_quote behavior + +// WARNING: setting $use_magic_quotes to 0 will make user-submitted +// data (for example, web form data) unsafe to pass directly +// into a MySQL database query. +$use_magic_quotes = 1; + +if( get_magic_quotes_gpc() && !$use_magic_quotes ) { + // force magic quotes to be removed + $_GET = array_map( 'sb_stripslashes_deep', $_GET ); + $_POST = array_map( 'sb_stripslashes_deep', $_POST ); + $_REQUEST = array_map( 'sb_stripslashes_deep', $_REQUEST ); + $_COOKIE = array_map( 'sb_stripslashes_deep', $_COOKIE ); + } +else if( !get_magic_quotes_gpc() && $use_magic_quotes ) { + // force magic quotes to be added + $_GET = array_map( 'sb_addslashes_deep', $_GET ); + $_POST = array_map( 'sb_addslashes_deep', $_POST ); + $_REQUEST = array_map( 'sb_addslashes_deep', $_REQUEST ); + $_COOKIE = array_map( 'sb_addslashes_deep', $_COOKIE ); + } + + + +// set to NULL so we can detect when we have set it on purpose +global $return_url; +$return_url = NULL; + + +// deal with cookies for logins + +// ignore cookies if $loggedInID already set by another part of the script +global $loggedInID; + + +// set by the logout script to tell us to ignore cookies +global $justLoggedOut; + + +$cookieName = $tableNamePrefix . "cookie"; + +$cookie_user_id = ""; +if( isset( $_COOKIE[ $cookieName ."_user_id" ] ) ) { + $cookie_user_id = $_COOKIE[ $cookieName ."_user_id" ]; + } +$cookie_session_id = ""; +if( isset( $_COOKIE[ $cookieName ."_session_id" ] ) ) { + $cookie_session_id = $_COOKIE[ $cookieName ."_session_id" ]; + } + +if( ! $justLoggedOut && + strcmp( $loggedInID, "" ) == 0 ) { // $loggedInID not already set + + $loggedInID = sb_getLoggedInUser(); + + if( strcmp( $loggedInID, "" ) != 0 ) { + // push the cookie expiration forward + sb_refreshCookie( $cookie_user_id, $cookie_session_id ); + } + } + + + +/** + * Displays either a login form or information about the currently logged-in + * user (along with a logout link). + */ +function seedBlogs_showLoginBox() { + global $loggedInID, $justLoggedOut, $tableNamePrefix; + + // don't use global $return_url here + $return_url = sb_getReturnURL(); + + if( sb_getUserCount() == 0 ) { + // no registered users + // show link to register form + + // use main site URL as return URL here + // This avoid redirecting the user back to sb_setup + global $mainSiteURL; + $encoded_return_url= urlencode( $mainSiteURL ); + + echo "[". + "Create Admin Account]"; + } + else if( $justLoggedOut || strcmp( $loggedInID, "" ) == 0 ) { + $encoded_return_url= urlencode( $return_url ); + + // show the login form +?> + +
+ + + + + + + + + + +
User ID:
Password:
+ [New Account]
+ [Forgot Password?]
+
+ +" . sb_stripMagicQuotes( $loggedInID ) . + "
\n"; + echo "[Log Out] "; + echo "[". + "Edit Account]\n"; + + if( sb_isAdministrator() ) { + // show a link to pending account queue, if any are pending + sb_connectToDatabase(); + $query = + "SELECT COUNT(*) FROM $tableNamePrefix"."users ". + "WHERE approved = '0';"; + $result = sb_queryDatabase( $query ); + $pendingCount = mysql_result( $result, 0, 0 ); + + sb_closeDatabase(); + + if( $pendingCount > 0 ) { + $countString = "$pendingCount account requests"; + if( $pendingCount == 1 ) { + $countString = "$pendingCount account request"; + } + echo "
[" . + "$countString waiting]"; + } + + // show a link to pending post queue, if any are waiting + sb_connectToDatabase(); + $query = + "SELECT COUNT(*) FROM $tableNamePrefix"."posts ". + "WHERE approved = '0' AND removed = '0';"; + $result = sb_queryDatabase( $query ); + $pendingCount = mysql_result( $result, 0, 0 ); + + sb_closeDatabase(); + + if( $pendingCount > 0 ) { + $countString = "$pendingCount posts"; + if( $pendingCount == 1 ) { + $countString = "$pendingCount post"; + } + echo "
[" . + "$countString pending approval]"; + } + + echo "
[" . + "Manage Accounts]
"; + } + } + } + + + +/** + * Displays the search box (used to search all seedBlogs). + * + * @param $inFieldWidth the width of the field, in characters. + * Defaults to 15. + * @param $inShowButton true to show the "Search" button, or false + * to hide it. + * Defaults to true. + */ +function seedBlogs_showSearchBox( $inFieldWidth = 15, $inShowButton = true ) { + // redisplay key words if they are present as POSTed variables + $key_words = ""; + if( isset( $_REQUEST[ "key_words" ] ) ) { + $key_words = + sb_stripMagicQuotes( + sb_getRequestVariableRaw( "key_words" ) ); + } +?> +
+ + + NAME="key_words" + VALUE=""> +"; + } + echo "
"; + } + + + +/** + * Displays a seed blog with default formatting options + * + * @param $inBlogName the name of the blog in the database. Should not + * contain spaces or special characters. + * @param $inShowIntroText 1 to show intro text under headlines, or 0 to + * show only headlines. + * @param $inShowAuthors (only applies if $inShowIntroText is 1) 1 to show + * the author of each post, or 0 to hide the authors. + * Defaults to 1. + * @param $inShowDates (only applies if $inShowIntroText is 1) 1 to show + * the creation date for each post, or 0 to hide the dates. + * Defaults to 1. + * @param $inOrder 1 to order by creation date with newest posts first, + * -1 to order by creation date with oldest posts first, + * 0 to order by expiration date with oldest posts first, or + * 2 to allow the administrators to tweak the ordering (up/down widgets + * will be displayed to allow admins to move posts up and down in the list). + * Defaults to 1. + * @param $inMaxNumber the maximum number of entries to show. -1 specifies + * no limit. + * Defaults to 10. + * @param $inNumToSkip the number of posts to skip, starting at the top + * of the list. Specifying 0 shows $inMaxNumber posts starting with the + * top post. Defaults to 0. + * @param $inShowArchive 1 to show the archive link, or 0 to hide it. + * Defaults to 1. + * @param $inShowSubmitLinkToPublic 1 to show a link for the public to submit + * posts, or 0 to hide the link. + * Defaults to 1. + */ +function seedBlog( $inBlogName, + $inShowIntroText, + $inShowAuthors = 1, + $inShowDates = 1, + $inOrder = 1, + $inMaxNumber = 10, + $inNumToSkip = 0, + $inShowArchive = 1, + $inShowSubmitLinkToPublic = 1 ) { + + global $storyBlockFormatOpen, $storyBlockFormatClose, + $headlineFormatOpen, $headlineFormatClose, $textBlockFormatOpen, + $textBlockFormatClose, $storySeparator, + $linkStoryBlockFormatOpen, $linkStoryBlockFormatClose, + $linkHeadlineFormatOpen, $linkHeadlineFormatClose, $linkStorySeparator; + + // pick from defaults depending on whether intro text is shown or not + $local_storyBlockFormatOpen = $linkStoryBlockFormatOpen; + $local_storyBlockFormatClose = $linkStoryBlockFormatClose; + $local_headlineFormatOpen = $linkHeadlineFormatOpen; + $local_headlineFormatClose = $linkHeadlineFormatClose; + $local_storySeparator = $linkStorySeparator; + + + if( $inShowIntroText ) { + $local_storyBlockFormatOpen = $storyBlockFormatOpen; + $local_storyBlockFormatClose = $storyBlockFormatClose; + $local_headlineFormatOpen = $headlineFormatOpen; + $local_headlineFormatClose = $headlineFormatClose; + $local_storySeparator = $storySeparator; + } + + + seedBlogFormatted( $inBlogName, + $inShowIntroText, + $inShowAuthors, + $inShowDates, + $inOrder, + $inMaxNumber, + $inNumToSkip, + $inShowArchive, + $inShowSubmitLinkToPublic, + $local_storyBlockFormatOpen, + $local_storyBlockFormatClose, + $local_headlineFormatOpen, + $local_headlineFormatClose, + $textBlockFormatOpen, + $textBlockFormatClose, + $local_storySeparator ); + } + + + + +/** + * Displays a seed blog with customized formatting options. + * + * Parameters are the same as for the simpler call above, except: + * @param $inStoryBlockFormatOpen opening HTML used to format each story block. + * @param $inStoryBlockFormatClose closing HTML used to format each story + * block. + * @param $inHeadlineFormatOpen opening HTML used to format headlines. + * @param $inHeadlineFormatClose closing HTML used to format headlines. + * @param $inTextBlockFormatOpen opening HTML used to format the text of a + * post under the headline. Ignored if $inShowIntroText = 0. + * @param $inTextBlockFormatClose closing HTML used to format the text of a + * post under the headline. Ignored if $inShowIntroText = 0. + * @param $inStorySeparator HTML to insert between each story block in a story + * list. + */ +function seedBlogFormatted( $inBlogName, + $inShowIntroText, + $inShowAuthors, + $inShowDates, + $inOrder, + $inMaxNumber, + $inNumToSkip, + $inShowArchive, + $inShowSubmitLinkToPublic, + // formatting options: + $inStoryBlockFormatOpen, + $inStoryBlockFormatClose, + $inHeadlineFormatOpen, + $inHeadlineFormatClose, + $inTextBlockFormatOpen, + $inTextBlockFormatClose, + $inStorySeparator ) { + + global $return_url; + + if( $return_url == NULL ) { + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + } + + // display link for posting new item + $postLinkName = "Submit"; + $postLinkHint = "Submit a post into the approval queue"; + + $isCommentBlog = false; + if( preg_match( "/_comments/", $inBlogName ) ) { + $isCommentBlog = true; + $postLinkName = "Submit Comment"; + $postLinkHint = "Submit a comment into the approval queue"; + } + $allowPost = false; + + global $loggedInID, $autoApprovePosts, $allowSubmissionsFromPublic; + global $tableNamePrefix; + + if( strcmp( $loggedInID, "" ) != 0 ) { + if( $autoApprovePosts || + sb_getUserDatabaseField( $loggedInID, + "administrator" ) == 1 ) { + // post, don't submit + $postLinkName = "New Post"; + $postLinkHint = "Add a new post"; + if( $isCommentBlog ) { + $postLinkName = "Post Comment"; + $postLinkHint = "Add a new comment"; + } + } + $allowPost = true; + } + else { + // no one logged in + if( $inShowSubmitLinkToPublic && $allowSubmissionsFromPublic || + $isCommentBlog ) { + $allowPost = true; + } + } + + if( $allowPost ) { + echo "[" . + "$postLinkName]
"; + } + if( sb_isAdministrator() ) { + // show link to queue, if there are posts wating + + sb_connectToDatabase(); + $query = + "SELECT COUNT(*) FROM $tableNamePrefix"."posts ". + "WHERE approved = \"0\" AND removed = \"0\" AND ". + "blog_name = \"$inBlogName\";"; + $result = sb_queryDatabase( $query ); + $pendingCount = mysql_result( $result, 0, 0 ); + + sb_closeDatabase(); + + if( $pendingCount > 0 ) { + $countString = "$pendingCount posts"; + if( $pendingCount == 1 ) { + $countString = "$pendingCount post"; + } + echo "[" . + "$countString in queue]
"; + } + } + + if( $inShowIntroText ) { + // extra space + //echo "
"; + } + + // get blog posts from the database + + $orderClause = "ORDER BY creation_date DESC"; + + if( $inOrder == 0 ) { + $orderClause = "ORDER BY expiration_date ASC"; + } + if( $inOrder == -1 ) { + $orderClause = "ORDER BY creation_date ASC"; + } + + $limitNumber = $inMaxNumber; + + if( $inMaxNumber == -1 ) { + // use a large number, as suggested in the MySQL docs, to cause + // limit to be ignored + $limitNumber = 99999; + } + + // LIMIT is only supported by MySQL + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE approved = '1' AND removed = '0' ". + "AND blog_name = '$inBlogName' ". + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL ) " . + "$orderClause LIMIT $inNumToSkip, $limitNumber;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + if( $numRows == 0 ) { + echo "[no posts]
"; + } + + $mapArray = NULL; + if( $inOrder == 2 ) { + // use map and ignore the above query + $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ". + "WHERE blog_name = '$inBlogName';"; + sb_connectToDatabase(); + + $result = sb_queryDatabase( $mapQuery ); + + $mapRaw = ""; + + if( mysql_numrows( $result ) == 0 ) { + // no order_map entry yet for this blog + + // insert a new map containing an empty string + $mapQuery = "INSERT INTO $tableNamePrefix"."order_map ". + "VALUES ( " . + "'$inBlogName', '' );"; + sb_queryDatabase( $mapQuery ); + } + else { + $mapRaw = mysql_result( $result, 0, 0 ); + } + + sb_closeDatabase(); + + + $mapArrayRaw = preg_split( "/\s+/", $mapRaw ); + + // filter the map array to remove unapproved, removed, or expired + // post_ids + $map = ""; + + sb_connectToDatabase(); + for( $i=0; $i $inMaxNumber ) { + + $numRows = $inMaxNumber; + } + } + + // finally, display the posts, using either the query results or the + // map + for( $i=0; $i<$numRows; $i++ ) { + + $subject_line = ""; + $post_id = ""; + $intro_text = ""; + $body_text = ""; + $user_id = ""; + $date = ""; + $allow_comments = ""; + $show_permalink = ""; + + if( $mapArray == NULL ) { + // use the query results + $subject_line = mysql_result( $result, $i, "subject_line" ); + $post_id = mysql_result( $result, $i, "post_id" ); + $intro_text = mysql_result( $result, $i, "intro_text" ); + $body_text = mysql_result( $result, $i, "body_text" ); + + $user_id = mysql_result( $result, $i, "user_id" ); + $date = mysql_result( $result, $i, "creation_date" ); + $allow_comments = mysql_result( $result, $i, "allow_comments" ); + $show_permalink = mysql_result( $result, $i, "show_permalink" ); + } + else { + // ignore query results + // re-query database according to map + $post_id = $mapArray[ $i + $inNumToSkip ]; + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE post_id = '$post_id';"; + sb_connectToDatabase(); + $singleResult = sb_queryDatabase( $query ); + sb_closeDatabase(); + + $subject_line = mysql_result( $singleResult, 0, "subject_line" ); + $post_id = mysql_result( $singleResult, 0, "post_id" ); + $intro_text = mysql_result( $singleResult, 0, "intro_text" ); + $body_text = mysql_result( $singleResult, 0, "body_text" ); + + $user_id = mysql_result( $singleResult, 0, "user_id" ); + $date = mysql_result( $singleResult, 0, "creation_date" ); + $allow_comments = + mysql_result( $singleResult, 0, "allow_comments" ); + $show_permalink = + mysql_result( $singleResult, 0, "show_permalink" ); + } + + // trim leading/trailing whitespace + $subject_line = trim( $subject_line ); + $intro_text = trim( $intro_text ); + $body_text = trim( $body_text ); + + + if( $inShowIntroText ) { + + $author = NULL; + if( $inShowAuthors ) { + $author = $user_id; + } + $dateString = NULL; + if( $inShowDates ) { + $dateString = $date; + } + + $showUpDownWidgets = + ( $inOrder == 2 && sb_isAdministrator() ); + + $index = $i + $inNumToSkip; + + // show up widget if we are down from the top + $showUpWidget = + ( $index > 0 ) && + $showUpDownWidgets; + + // show down widget if we are up from the bottom + $showDownWidget = + ( $index < count( $mapArray ) - 1 ) && + $showUpDownWidgets; + + sb_generateStoryBlock( $inBlogName, + $post_id, + $subject_line, + $author, + $dateString, + $showUpWidget, + $showDownWidget, + $intro_text, + $body_text, + 0, // show link to body text + $allow_comments, + $show_permalink, + $return_url, + // formatting options: + $inStoryBlockFormatOpen, + $inStoryBlockFormatClose, + $inHeadlineFormatOpen, + $inHeadlineFormatClose, + $inTextBlockFormatOpen, + $inTextBlockFormatClose ); + + } + else { + $linkTarget = "seedBlogs.php?action=display_post&". + "post_id=$post_id". + "&show_author=$inShowAuthors&show_date=$inShowDates"; + $directURLTarget = false; + + if( $intro_text != NULL && $body_text == NULL ) { + // we just have intro text and no body. + // check if the intro text contains just a URL + + // intro text has already been trimmed of leading/trailing + // whitespace above + + if( strstr( $intro_text, "http://" ) != false && + strpos( $intro_text, "http://" ) == 0 && + strstr( $intro_text, " " ) == false ) { + // intro text starts with URL and contains nothing else + + // make a direct link + $linkTarget = trim( $intro_text ); + + $directURLTarget = true; + } + } + + // open a story block for the headline + echo "$inStoryBlockFormatOpen"; + + // link around subject, with formatting inside link tags + echo "$inHeadlineFormatOpen". + "$subject_line". + "$inHeadlineFormatClose"; + + if( $directURLTarget && sb_canEdit( $post_id ) ) { + // problem: clicking a direct URL link takes you to the URL + // and not the display page, so there is no + // way to edit the post. + + // add a special edit link to these posts + echo " [" . + "Edit]"; + } + + if( $inOrder == 2 && sb_isAdministrator() ) { + // show up/down widgets + $index = $i + $inNumToSkip; + + $upShown = false; + if( $index > 0 ) { + echo " [" . + "Up]"; + $upShown = true; + } + if( $index < count( $mapArray ) - 1 ) { + if( ! $upShown ) { + // insert space to separate down widget from headline + echo " "; + } + echo "[" . + "Down]"; + } + } + + echo "$inStoryBlockFormatClose"; + } + + + if( $i < $numRows - 1 ) { + // separate from next story + echo "$inStorySeparator"; + } + } + + + + if( $inShowArchive ) { + // count total number of posts to see if we need the archive link + + $postCount = 0; + + if( $mapArray == NULL ) { + sb_connectToDatabase(); + $query = + "SELECT COUNT(*) FROM $tableNamePrefix"."posts ". + "WHERE approved = '1' ". + "AND removed = '0' AND blog_name = '$inBlogName' " . + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL );"; + $result = sb_queryDatabase( $query ); + $postCount = mysql_result( $result, 0, 0 ); + + sb_closeDatabase(); + } + else { + $postCount = count( $mapArray ); + } + + $numOlderPosts = $postCount - ( $inNumToSkip + $numRows ); + + if( $numOlderPosts > 0 ) { + // there are more posts in the archive + + if( $inShowIntroText ) { + // extra space + echo "

"; + } + + // archive pages have 10 posts each + // show link to archive + $offset = $inNumToSkip + $numRows; + echo "[$numOlderPosts ". + "in Archive]
"; + } + } + } + + + +/** + * Generates a URL to the RSS 2.0 feed for a given seedBlog. + * GETing this URL will return RSS XML. + * + * Order of RSS feed is fixed to "order by creation date", with + * newest posts listed first. + * + * @param $inBlogName the name of the blog in the database. Should not + * contain spaces or special characters. + * @param $inChannelTitle the name of the RSS channel. + * @param $inChannelDescription the description of the RSS channel. + * @param $inMaxNumber the maximum number of items to include in the feed. + * -1 specifies no limit. + * Defaults to 10. + * @param $inShowAuthors 1 to show authors, or 0 to hide them. Defaults to 1. + * @param $inShowDates 1 to show dates, or 0 to hide them. Defaults to 1. + */ +function seedBlogRSSLink( $inBlogName, + $inChannelTitle, + $inChannelDescription, + $inMaxNumber = 10, + $inShowAuthors = 1, + $inShowDates = 1 ) { + + $encodedTitle = urlencode( $inChannelTitle ); + $encodedDescription = urlencode( $inChannelDescription ); + $urlParams = + "?action=rss_feed&". + "blog_name=$inBlogName&". + "channel_title=$encodedTitle&" . + "channel_description=$encodedDescription&". + "max_number=$inMaxNumber&show_authors=$inShowAuthors&". + "show_dates=$inShowDates"; + + global $fullSeedBlogsURL; + + return $fullSeedBlogsURL . $urlParams; + } + + + +/** + * Just like seedBlogRSSLink, but generates full HTML for an RSS button. + * + * Call this wherever you want an RSS button to appear on your page. + */ +function seedBlogRSSButton( $inBlogName, + $inChannelTitle, + $inChannelDescription, + $inMaxNumber = 10, + $inShowAuthors = 1, + $inShowDates = 1 ) { + + $rss_url = seedBlogRSSLink( $inBlogName, + $inChannelTitle, + $inChannelDescription, + $inMaxNumber, + $inShowAuthors, + $inShowDates ); + echo "". + "
". + "". + "
". + "". + "
". + "RSS 2.0". + "
"; + } + + + +// end of functions that might be called externally by end-users + + + + + + +// general processing whenver seedBlogs.php is accessed directly + +// grab POST/GET variables +$action = ""; +if( isset( $_REQUEST[ "action" ] ) ) { + $action = sb_getRequestVariableSafe( "action" ); + } +$post_id = ""; +if( isset( $_REQUEST[ "post_id" ] ) ) { + $post_id = sb_getRequestVariableSafe( "post_id" ); + } +$blog_name = ""; +if( isset( $_REQUEST[ "blog_name" ] ) ) { + $blog_name = sb_getRequestVariableSafe( "blog_name" ); + } + +global $return_url; +$return_url = ""; +if( isset( $_REQUEST[ "return_url" ] ) ) { + $return_url = sb_getRequestVariableSafe( "return_url" ); + } + + +if( strcmp( $post_id, "" ) == 0 ) { + $post_id = NULL; + } + +if( strcmp( $action, "version" ) == 0 ) { + global $seedBlogs_version; + echo "$seedBlogs_version"; + } +else if( strcmp( $action, "login" ) == 0 ) { + sb_login(); + } +else if( strcmp( $action, "logout" ) == 0 ) { + sb_logout(); + } +else if( strcmp( $action, "show_register_form" ) == 0 ) { + sb_showRegisterForm( "" ); + } +else if( strcmp( $action, "show_password_help_form" ) == 0 ) { + sb_showPasswordHelpForm( "" ); + } +else if( strcmp( $action, "send_password_email" ) == 0 ) { + sb_sendPasswordEmail( "" ); + } +else if( strcmp( $action, "register" ) == 0 ) { + sb_register(); + } +else if( strcmp( $action, "setup_database" ) == 0 ) { + sb_setupDatabase(); + } +else if( strcmp( $action, "edit_post" ) == 0 ) { + sb_showEditor( $blog_name, $post_id ); + } +else if( strcmp( $action, "update_post" ) == 0 ) { + sb_updatePost( $blog_name, $post_id ); + } +else if( strcmp( $action, "move_up" ) == 0 ) { + sb_moveUp( $blog_name, $post_id ); + } +else if( strcmp( $action, "move_down" ) == 0 ) { + sb_moveDown( $blog_name, $post_id ); + } +else if( strcmp( $action, "display_post" ) == 0 ) { + sb_displayPost( $post_id ); + } +else if( strcmp( $action, "show_archive" ) == 0 ) { + sb_showArchive( $blog_name ); + } +else if( strcmp( $action, "approve_post" ) == 0 ) { + sb_approvePost( $post_id ); + } +else if( strcmp( $action, "approve_account" ) == 0 ) { + sb_approveAccount(); + } +else if( strcmp( $action, "change_admin_status" ) == 0 ) { + sb_changeAdminStatus(); + } +else if( strcmp( $action, "remove_account" ) == 0 ) { + sb_removeAccount(); + } +else if( strcmp( $action, "show_post_queue" ) == 0 ) { + sb_showPostQueue( $blog_name ); + } +else if( strcmp( $action, "show_account_queue" ) == 0 ) { + sb_showAccountQueue(); + } +else if( strcmp( $action, "show_account_list" ) == 0 ) { + sb_showAccountList(); + } +else if( strcmp( $action, "search" ) == 0 ) { + sb_search(); + } +else if( strcmp( $action, "rss_feed" ) == 0 ) { + sb_rssFeed(); + } +else if( strcmp( $action, "sb_setup" ) == 0 ) { + global $header, $footer; + //include_once( $header ); + global $setup_header, $setup_footer; + echo $setup_header; + + echo "

seedBlogs Web-based Setup

"; + + echo "Creating tables:
"; + + echo "
+
+ +
"; + + sb_setupDatabase(); + + echo "


"; + + echo "After you create an admin account, the setup process will be ". + "complete.

"; + + echo "Step 2: "; + + echo "
"; + + seedBlogs_showLoginBox(); + + echo "
"; + + echo $setup_footer; + //include_once( $footer ); + } +else if( preg_match( "/seedBlogs\.php/", $_SERVER[ "SCRIPT_NAME" ] ) ) { + // seedBlogs.php has been called without an action parameter + + // the preg_match ensures that seedBlogs.php was called directly and + // not just included by another script + + // quick (and incomplete) test to see if we should show ins + global $tableNamePrefix; + + // check if our "posts" table exists + $tableName = $tableNamePrefix . "posts"; + sb_connectToDatabase(); + + $exists = sb_doesTableExist( $tableName ); + sb_closeDatabase(); + + if( $exists ) { + + // show main page + global $mainSiteURL; + // redirect + header( "Location: $mainSiteURL" ); + } + else { + // start the setup procedure + + global $header, $footer; + //include_once( $header ); + global $setup_header, $setup_footer; + echo $setup_header; + + echo "

seedBlogs Web-based Setup

"; + + echo "seedBlogs will walk you through a brief setup process.

"; + + echo "Step 1: ". + "". + "create the database tables"; + + echo $setup_footer; + //include_once( $footer ); + } + + } + + + +/** + * Creates the database tables needed by seedBlogs. + */ +function sb_setupDatabase() { + global $tableNamePrefix; + + // make sure our "posts" table exists + $tableName = $tableNamePrefix . "posts"; + sb_connectToDatabase(); + if( ! sb_doesTableExist( $tableName ) ) { + + // this table contains all the information for each post + $query = + "CREATE TABLE $tableName(" . + "post_id VARCHAR(255) NOT NULL PRIMARY KEY," . + "blog_name VARCHAR(255) NOT NULL," . + "user_id VARCHAR(20) NOT NULL," . + "creation_date DATETIME NOT NULL," . + "change_date DATETIME NOT NULL," . + "expiration_date DATETIME," . + "allow_comments TINYINT NOT NULL," . + "show_permalink TINYINT NOT NULL," . + "approved TINYINT NOT NULL," . + "removed TINYINT NOT NULL," . + "subject_line VARCHAR(60) NOT NULL," . + "intro_text LONGTEXT," . + "body_text LONGTEXT );"; + + $result = sb_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + + $tableName = $tableNamePrefix . "users"; + if( ! sb_doesTableExist( $tableName ) ) { + + // this table contains information for each user + $query = + "CREATE TABLE $tableName(" . + "user_id VARCHAR(20) NOT NULL PRIMARY KEY," . + "password_md5 CHAR(32) NOT NULL,". + "email VARCHAR(255),". + "session_id CHAR(32) NULL,". + "approved TINYINT NOT NULL," . + "administrator TINYINT NOT NULL );"; + + $result = sb_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + + $tableName = $tableNamePrefix . "order_map"; + if( ! sb_doesTableExist( $tableName ) ) { + + // this table contains order information for each blog + $query = + "CREATE TABLE $tableName(" . + "blog_name VARCHAR(255) NOT NULL PRIMARY KEY," . + "map LONGTEXT NOT NULL );"; + + // each map field contains a list of post_ids separated by whitespace + + $result = sb_queryDatabase( $query ); + + echo "$tableName table created
"; + } + else { + echo "$tableName table already exists
"; + } + + sb_closeDatabase(); + + } + + + +/** + * Logs a user in (setting the global $loggedInID) according to + * the POSTED variables. + */ +function sb_login() { + // the body of this function was largely copied from the NCN project + + // grab posted variables + + $user_id = sb_getRequestVariableSafe( "user_id" ); + // never used in database query, so strip once here + $password = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password" ) ); + + if( sb_doesUserExist( $user_id ) ) { + + if( sb_getUserDatabaseField( $user_id, "approved" ) == 0 ) { + // display failure page + sb_messagePage( "User ID " . + sb_stripMagicQuotes( $user_id ) . + " has no been approved yet." ); + } + else { + + $passwordMD5 = sb_computePasswordHash( sb_stripMagicQuotes( $user_id ), + $password ); + + $truePasswordMD5 = sb_getUserDatabaseField( $user_id, + "password_md5" ); + + if( strcmp( $truePasswordMD5, $passwordMD5 ) == 0 ) { + + $session_id = sb_computeSessionID( sb_stripMagicQuotes( $user_id ), + $password ); + + sb_setUserDatabaseField( $user_id, "session_id", $session_id ); + + // set cookies with the user_id and session_id + sb_refreshCookie( $user_id, $session_id ); + + // set global + global $loggedInID; + $loggedInID = $user_id; + + // show page user logged in from + + // redirect + global $return_url; + header( "Location: $return_url" ); + } + else { + // display failure page + sb_messagePage( "Log in failed." ); + } + } + } + else { + // display failure page + sb_messagePage( "User ID " . + sb_stripMagicQuotes( $user_id ) . + " does not exist." ); + } + } + + + + + + +/** + * Logs the current user out and clears cookies. + */ +function sb_logout() { + // clear cookie in user's browser + sb_clearCookie(); + + global $justLoggedOut, $loggedInID; + + // clear the session id in the database + sb_setUserDatabaseField( $loggedInID, "session_id", NULL ); + + // tell other parts of script to ignore set cookies + $justLoggedOut = 1; + + // drop the ID that we have read from the cookies so that + // the messagePage can reflect the fact that the user has logged out + $loggedInID = ""; + + sb_messagePage( "You have successfully logged out." ); + } + + + +/** + * Shows the user registration form, or shows the account editing form + * if a user is already logged in. + * + * @param inMessage the message to display. + */ +function sb_showRegisterForm( $inMessage ) { + global $header, $footer; + + include_once( $header ); + + echo "$inMessage"; + + global $loggedInID, $tableNamePrefix; + + $emailValue = ""; + $editExisting = false; + $buttonName = "Register"; + + if( strcmp( $loggedInID, "" ) != 0 ) { + // user is already logged in + + // query to get the current email address + $query = "SELECT * FROM $tableNamePrefix"."users ". + "WHERE user_id = '$loggedInID';"; + sb_connectToDatabase(); + $result = sb_queryDatabase( $query ); + sb_closeDatabase(); + + $emailValue = mysql_result( $result, 0, "email" ); + $editExisting = true; + $buttonName = "Update"; + } + +?> +
+ +"; + + if( $editExisting ) { + echo ""; + } + + + echo ""; + + if( !$editExisting ) { +?> + + +"; + } +?> + + + + + + + +
User ID:
Leave blank to keep old password
Password:
Re-type Password:
Email:
+
+
+$inMessage
"; + + echo "Enter either your user ID or your email address:" +?> +
+ + + + + + + +
User ID:
Email:
+
+
+ 1 ) { + // more than one admin + // remind them of this fact to avoid confusion + + $adminListNotice = + "\nNote that these admins were all notified ". + "about this issue:\n". + "$userIDList\n"; + } + + global $siteName, $mainSiteURL, $siteEmailAddress; + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $emailList, "$siteName admin action needed", + "The following action is pending ". + "administrator approval:\n\n". + "$inMessage\n". + "$adminListNotice", + $mailHeaders ); + + } + + + +/** + * Sends out a password email using the POSTed variables. + */ +function sb_sendPasswordEmail() { + global $header, $footer; + + $user_id = sb_getRequestVariableSafe( "user_id" ); + + $email = sb_getRequestVariableSafe( "email" ); + + + $error = 0; + + // first, make sure the required fields are provided + if( strcmp( $user_id, "" ) == 0 && strcmp( $email, "" ) == 0 ) { + $error = 1; + sb_showPasswordHelpForm( "You must provide some account information." ); + } + + if( ! $error ) { + + // query to either find user with this ID + // or find all users with this email + $query = ""; + global $tableNamePrefix; + + if( strcmp( $user_id, "" ) != 0 ) { + $query = "SELECT * FROM $tableNamePrefix"."users ". + "WHERE user_id = '$user_id';"; + } + else { + $query = "SELECT * FROM $tableNamePrefix"."users ". + "WHERE email = '$email';"; + } + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + if( $numRows == 0 ) { + sb_showPasswordHelpForm( + "The information you entered does not match any account." ); + } + else if( $numRows > 1 ) { + sb_showPasswordHelpForm( + "More than one account uses this email address.
". + "You must provide a User ID." ); + } + else { + $user_id = mysql_result( $result, 0, "user_id" ); + $email = mysql_result( $result, 0, "email" ); + $password_md5 = mysql_result( $result, 0, "password_md5" ); + + // compute a new, temporary password + + // however, we need to generate a password that + // cannot be guessed by attackers + + // we can use the password MD5 sum (which we know) as a seed + + $temp_session_id = sb_computeSessionID( $user_id, $password_md5 ); + + // temp passwords are 10 hex digits long + // there are roughly 10^12 possible temp passwords + $temp_password = substr( $temp_session_id, 0, 10 ); + + $temp_password_md5 = + sb_computePasswordHash( sb_stripMagicQuotes( $user_id ), + $temp_password ); + sb_setUserDatabaseField( $user_id, + "password_md5", $temp_password_md5 ); + + global $siteName, $mainSiteURL, $siteEmailAddress; + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $email, "$siteName temporary password", + "Your password at $mainSiteURL has been ". + "reset.\n\n". + "Here is your temporary account information:\n\n". + "User ID: $user_id\n". + "Password: $temp_password\n", + $mailHeaders ); + + sb_messagePage( + "A temporary password has been sent to you by email." ); + } + } + } + + + +/** + * Processes the variables posted by the register form. + */ +function sb_register() { + global $tableNamePrefix, $loggedInID, $autoApproveUsers; + + $updateExisting = false; + if( strcmp( $loggedInID, "" ) != 0 ) { + $updateExisting = true; + } + + // grab posted variables + + $user_id = sb_getRequestVariableSafe( "user_id" ); + // never used in database query, so strip once here + $password = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password" ) ); + $password_b = sb_stripMagicQuotes( sb_getRequestVariableRaw( "password_b" ) ); + + $email = sb_getRequestVariableSafe( "email" ); + + $error = 0; + + // first, make sure the required fields are provided + if( !$updateExisting && strcmp( $user_id, "" ) == 0 ) { + $error = 1; + sb_showRegisterForm( "\"User ID\" is a required field." ); + } + else if( !$updateExisting && strcmp( $password, "" ) == 0 ) { + $error = 1; + sb_showRegisterForm( "You must enter a password." ); + } + else if( strcmp( $email, "" ) == 0 ) { + $error = 1; + sb_showRegisterForm( "You must enter an email address." ); + } + else if( strcmp( $password, $password_b ) != 0 ) { + $error = 1; + sb_showRegisterForm( "Your re-typed password does not match." ); + } + + if( ! $error ) { + if( !$updateExisting && sb_doesUserExist( $user_id ) ) { + + sb_showRegisterForm( "User id $user_id already exists." ); + } + else if( !$updateExisting && strcmp( $user_id, "Anonymous" ) == 0 ) { + + sb_showRegisterForm( "User id Anonymous is reserved." ); + } + else if( !$updateExisting ) { + $password_md5 = sb_computePasswordHash( sb_stripMagicQuotes( $user_id ), + $password ); + + $approved = 0; + $administrator = 0; + + if( sb_getUserCount() == 0 ) { + // auto admin and approve + $approved = 1; + $administrator = 1; + } + if( $autoApproveUsers ) { + $approved = 1; + } + $query = "INSERT INTO $tableNamePrefix". "users VALUES ( " . + "'$user_id', '$password_md5', '$email', NULL, ". + "'$approved', '$administrator' );"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + if( $approved ) { + // log the user in using same POST variables + sb_login(); + } + else { + // tell the user that their account registration is pending + // display failure page + + sb_messagePage( "Your account request has been sent to the ". + "administrators for approval.
". + "You will receive an email with further ". + "information." ); + + global $fullSeedBlogsURL, $emailAdminsAboutPendingItems; + + if( $emailAdminsAboutPendingItems ) { + sb_sendAdminNotice( + "The following new account is waiting for approval:\n". + "$user_id\n\n". + "After you log in, check the following link for ". + "details:\n". + "$fullSeedBlogsURL?action=show_account_queue" ); + } + } + + $approvalMessage = ""; + if( $approved ) { + $approvalMessage = + "Your account request has been auto-approved."; + } + else { + $approvalMessage = + "Your account is awaiting approval from the ". + "administrators. You will receive an email when your ". + "account is approved."; + } + // send an email with account information + global $siteName, $mainSiteURL, $siteEmailAddress; + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $email, "$siteName account requested", + "Your account request at $mainSiteURL has been ". + "received.\n\n". + "$approvalMessage\n\n". + "Here is your account information:\n\n". + "User ID: $user_id\n". + "Email: $email\n", + $mailHeaders ); + } + else { + // updating an existing account + + $passwordUpdate = ""; + if( strcmp( $password, "" ) != 0 ) { + // new password (already checked that $password_b matches) + $password_md5 = sb_computePasswordHash( + sb_stripMagicQuotes( $loggedInID ), $password ); + + $passwordUpdate = "password_md5 = '$password_md5', "; + } + $query = "UPDATE $tableNamePrefix". "users SET " . + "$passwordUpdate email = '$email' ". + "WHERE user_id = '$loggedInID';"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $passwordMessage = ""; + if( strcmp( $password, "" ) != 0 ) { + // log the user in using same POST variables + // need to do this to reset the cookie + sb_login(); + $passwordMessage = "(new password set)\n"; + } + else { + sb_messagePage( "Your account information has been updated.
". + "You will receive an email with your new ". + "information." ); + } + + // send an email with the updated account information + global $siteName, $mainSiteURL, $siteEmailAddress; + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $email, "$siteName account information updated", + "Your account information at $mainSiteURL has ". + "been updated.\n\n". + "Here is your new account information:\n\n". + "User ID: $user_id\n". + "$passwordMessage". + "Email: $email\n", + $mailHeaders ); + } + } + } + + + +/** + * Shows the editor form. + * + * @param $inBlogName the name of the blog to edit. + * @param $inPostID the postID to fill the form with, or NULL to + * show a blank form. + */ +function sb_showEditor( $inBlogName, $inPostID ) { + global $tableNamePrefix, $autoApprovePosts; + + $show_author = sb_getRequestVariableSafe( "show_author" ); + $show_date = sb_getRequestVariableSafe( "show_date" ); + + $blog_name = $inBlogName; + $author_name = ""; + $subject_line = ""; + $intro_text = ""; + $body_text = ""; + $expiration_date = NULL; + $allow_comments = 0; + // default to showing permalink + $show_permalink = 1; + $approved = 0; + $isExistingPost = false; + + // populate form fields from database + if( $inPostID != NULL ) { + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE post_id = '$inPostID';"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + if( mysql_numrows( $result ) != 1 ) { + sb_closeDatabase(); + sb_fatalError( "Post $inPostID does not exist in database." ); + } + + $row = mysql_fetch_array( $result, MYSQL_ASSOC ); + + $blog_name = $row[ "blog_name" ]; + $author_name = $row[ "user_id" ]; + $subject_line = $row[ "subject_line" ]; + $intro_text = $row[ "intro_text" ]; + $body_text = $row[ "body_text" ]; + $expiration_date = $row[ "expiration_date" ]; + $allow_comments = $row[ "allow_comments" ]; + $show_permalink = $row[ "show_permalink" ]; + $approved = $row[ "approved" ]; + + sb_closeDatabase(); + + $isExistingPost = true; + } + + $buttonName = "Submit for Approval"; + + if( $isExistingPost ) { + $buttonName = "Update"; + } + else { + if( sb_isAdministrator() || + ( $autoApprovePosts && strcmp( $loggedInID, "" ) != 0 ) ) { + // this is a direct post + $buttonName = "Post"; + } + } + + // include the header before generating a page + global $header, $footer; + include_once( $header ); + + global $return_url; +?> +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + +'; + } + + + if( ! preg_match( "/_comments/", $blog_name ) ) { + // no expiration dates allowed on comments +?> + + + + + + + + + + + + + + + + + + + + + +
> + Your Name: + > + +
> + Headline: + > + +
> +
+ +
> + Body Text:
+ +
> + Expires: + > + + > Never Expires +
>> + > Allow Comments +
>> + > Show Permanent Link +
>> + Approve Post +
>> + Remove Post +
+
+ +
+'; + } +?> + +
+ +$author_name is already in". + " use by a registered user." ); + } + else { + $post_id = sb_getUniquePostID(); + + global $autoApprovePosts, $loggedInID; + + if( strcmp( $loggedInID, "" ) != 0 ) { + if( sb_getUserDatabaseField( $loggedInID, + "administrator" ) == 1 ) { + // admin posts auto-approved + $postApproved = 1; + } + else { + if( $autoApprovePosts ) { + // approve all posts from logged-in users + $postApproved = 1; + } + } + } + + $user_id = "Anonymous"; + + if( strcmp( $loggedInID, "" ) != 0 ) { + $user_id = $loggedInID; + } + else { + // no one logged in, use author name if it is set + if( strcmp( $author_name, "" ) != 0 ) { + $user_id = $author_name; + } + } + + // this query is processed below, outside this if block + $query = "INSERT INTO $tableNamePrefix"."posts VALUES ( " . + "'$post_id', '$inBlogName', '$user_id', CURRENT_TIMESTAMP, " . + "CURRENT_TIMESTAMP, $expiration_date, '$allow_comments',". + "'$show_permalink', '$postApproved', ". + "\"0\", '$subject_line', " . + "'$intro_text', '$body_text' );"; + + + // update the map + // lock to ensure our update is atomic + $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ". + "WHERE blog_name = '$inBlogName' LOCK IN SHARE MODE;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $mapQuery ); + + if( mysql_numrows( $result ) == 1 ) { + $map = mysql_result( $result, 0, 0 ); + + // stick this post at the top of the list + $map = $post_id . "\n" . $map; + + $mapQuery = "UPDATE $tableNamePrefix"."order_map SET ". + "map = '$map' WHERE blog_name = '$inBlogName';"; + } + else { + // insert a new map containing only this post_id + $mapQuery = "INSERT INTO $tableNamePrefix"."order_map ". + "VALUES ( " . + "'$inBlogName', '$post_id' );"; + } + sb_queryDatabase( $mapQuery ); + + sb_closeDatabase(); + } + } + else { + // editing an existing post + $editingExisting = true; + + if( !sb_canEdit( $post_id ) ) { + $postAllowed = false; + + // display failure page + sb_messagePage( "You are not allowed to edit this post." ); + } + else { + // deal with approval and removal + $removedDataString = "removed = \"0\","; + if( $remove == 1 ) { + $removedDataString = "removed = \"1\","; + } + // default to not changing approval status + $approvedDataString = ""; + if( $approve == 1 && + sb_isAdministrator() ) { + $approvedDataString = "approved = \"1\","; + } + + $query = "UPDATE $tableNamePrefix"."posts SET " . + "change_date = CURRENT_TIMESTAMP, " . + "expiration_date = $expiration_date, " . + "allow_comments = '$allow_comments', ". + "show_permalink = '$show_permalink', ". + "$removedDataString " . + "$approvedDataString " . + "subject_line = '$subject_line', " . + "intro_text = '$intro_text', body_text = '$body_text' " . + "WHERE post_id = '$post_id';"; + } + } + + if( $postAllowed ) { + sb_connectToDatabase(); + + sb_queryDatabase( $query ); + + sb_closeDatabase(); + + if( $remove != 1 ) { + if( $postApproved == 1 || + sb_isAdministrator() || + $editingExisting ) { + + // display the updated post + + // redirect + header( "Location: $return_url" ); + } + else { + // let the user know the post has been submitted + sb_messagePage( "The post has been submitted for approval." ); + + + global $fullSeedBlogsURL, $emailAdminsAboutPendingItems; + + if( $emailAdminsAboutPendingItems ) { + sb_sendAdminNotice( + "A new post is waiting for approval:\n\n". + "After you log in, check the following link for ". + "details:\n". + "$fullSeedBlogsURL?action=show_post_queue". + "&blog_name=$inBlogName" ); + } + } + } + else { + // let the user know the post was removed + sb_messagePage( "The post has been removed." ); + } + } + } + + + +/** + * Moves a post up in the order map. + * + * @param $inBlogName the name of the blog. + * @param $inPostID the post to move up. + */ +function sb_moveUp( $inBlogName, $inPostID ) { + sb_movePost( $inBlogName, $inPostID, -1 ); + } + + + +/** + * Moves a post down in the order map. + * + * @param $inBlogName the name of the blog. + * @param $inPostID the post to move down. + */ +function sb_moveDown( $inBlogName, $inPostID ) { + sb_movePost( $inBlogName, $inPostID, 1 ); + } + + + +/** + * Moves a post in the order map. + * + * @param $inBlogName the name of the blog. + * @param $inPostID the post to move. + * @param $inMoveDirection -1 for up, or 1 for down. + */ +function sb_movePost( $inBlogName, $inPostID, $inMoveDirection ) { + global $return_url, $tableNamePrefix, $loggedInID; + // update the map + // lock to ensure our update is atomic + $mapQuery = "SELECT map FROM $tableNamePrefix"."order_map ". + "WHERE blog_name = '$inBlogName' LOCK IN SHARE MODE;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $mapQuery ); + + if( mysql_numrows( $result ) == 1 ) { + $map = mysql_result( $result, 0, 0 ); + + + $mapArray = preg_split( "/\s+/", $map ); + // move this post up in the list, skipping over expired, removed, + // or unapproved posts + + // first, find the index of our post + $postIndex = -1; + + for( $i=0; $i 0 ) { + echo ""; + echo "
"; + echo ""; + sb_showComments( $inPostID ); + echo "
"; + } + } + + //echo ""; + + echo "
"; + + include_once( $footer ); + } + + + +/** + * Displays an archive for a blog using posted values to specify the range + * of posts to list. + * + * @param $inBlogName the name of the blog to show an archive for. + */ +function sb_showArchive( $inBlogName ) { + + $offset = sb_getRequestVariableSafe( "offset" ); + $count = sb_getRequestVariableSafe( "count" ); + $order = sb_getRequestVariableSafe( "order" ); + $show_intro = sb_getRequestVariableSafe( "show_intro" ); + $show_authors = sb_getRequestVariableSafe( "show_authors" ); + $show_dates = sb_getRequestVariableSafe( "show_dates" ); + $show_submit_link_to_public = + sb_getRequestVariableSafe( "show_submit_link_to_public" ); + + // this archive page should be the return destination after edits + global $return_url; + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + + // now simply display a seedBlog with the appropriate offset + + global $header, $footer; + + include( $header ); + + echo "
"; + seedBlog( $inBlogName, + $show_intro, + $show_authors, + $show_dates, + $order, + $count, + $offset, + 1, // show the archive + $show_submit_link_to_public ); + echo "
"; + include( $footer ); + } + + + +/** + * Approves a post that is waiting in the admin queue. + * + * @param $inPostID the post to update, or NULL to + * insert a new post. + */ +function sb_approvePost( $inPostID ) { + global $return_url, $tableNamePrefix; + + + $post_id = $inPostID; + + $query = ""; + + global $header, $footer; + + $approvalAllowed = true; + + if( $post_id == NULL ) { + + $approvalAllowed = false; + + // display failure page + sb_messagePage( "No post_id field given." ); + } + else { + + if( !sb_isAdministrator() ) { + + $approvalAllowed = false; + + // display failure page + sb_messagePage( "You must be an administrator to approve posts." ); + } + else { + $query = "UPDATE $tableNamePrefix"."posts SET " . + "approved = '1' " . + "WHERE post_id = '$post_id';"; + } + } + + if( $approvalAllowed ) { + sb_connectToDatabase(); + + sb_queryDatabase( $query ); + + sb_closeDatabase(); + + // redirect to return URL + header( "Location: $return_url" ); + } + } + + + +/** + * Approves an account that is waiting in the admin queue according to POSTed + * values + */ +function sb_approveAccount() { + global $return_url, $tableNamePrefix; + + $user_id = sb_getRequestVariableSafe( "user_id" ); + $admin = sb_getRequestVariableSafe( "admin" ); + + + $query = ""; + + global $header, $footer; + + $approvalAllowed = true; + + if( $user_id == NULL ) { + + $approvalAllowed = false; + + // display failure page + sb_messagePage( "No user_id field given." ); + } + else { + + if( !sb_isAdministrator() ) { + + $approvalAllowed = false; + + // display failure page + sb_messagePage( "You must be an administrator to approve accounts." ); + } + else { + $adminClause = ""; + + if( $admin == 1 ) { + $adminClause = ", administrator = '1' "; + } + + $query = "UPDATE $tableNamePrefix" . "users SET " . + "approved = '1' $adminClause" . + "WHERE user_id = '$user_id';"; + } + } + + if( $approvalAllowed ) { + sb_connectToDatabase(); + + sb_queryDatabase( $query ); + + sb_closeDatabase(); + + // send an email indicating approval + global $siteName, $mainSiteURL, $siteEmailAddress; + + $email = sb_getUserDatabaseField( $user_id, "email" ); + + $adminMessage = ""; + if( $admin ) { + $adminMessage = "You have been designated as an administrator."; + } + + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $email, "$siteName account approved", + "Your account request at $mainSiteURL has been ". + "approved.\n\n". + "$adminMessage\n", + $mailHeaders ); + + // redirect to return URL + header( "Location: $return_url" ); + } + } + + + +/** + * Changes the admin status of an account according to POSTed values. + */ +function sb_changeAdminStatus() { + global $return_url, $tableNamePrefix; + + $user_id = sb_getRequestVariableSafe( "user_id" ); + $admin = sb_getRequestVariableSafe( "admin" ); + + + $query = ""; + + $approvalAllowed = true; + + if( $user_id == NULL || $admin == NULL ) { + + // display failure page + sb_messagePage( "Required fields are missing." ); + } + else { + + if( !sb_isAdministrator() ) { + + $approvalAllowed = false; + + // display failure page + sb_messagePage( "You must be an administrator to change accounts." ); + } + else { + $adminClause = "administrator = '0'"; + + if( $admin == 1 ) { + $adminClause = "administrator = '1'"; + } + + $query = "UPDATE $tableNamePrefix" . "users SET" . + " $adminClause " . + "WHERE user_id = '$user_id';"; + } + } + + if( $approvalAllowed ) { + sb_connectToDatabase(); + + sb_queryDatabase( $query ); + + sb_closeDatabase(); + + // send an email indicating the change + global $siteName, $mainSiteURL, $siteEmailAddress; + + $email = sb_getUserDatabaseField( $user_id, "email" ); + + $adminMessage = ""; + if( $admin ) { + $adminMessage = "You have been designated as an administrator."; + } + else { + $adminMessage = "Your administrator status has been revoked."; + } + + $mailHeaders = "From: $siteEmailAddress"; + $result = mail( $email, "$siteName account changed", + "Your request at $mainSiteURL has been ". + "changed.\n\n". + "$adminMessage\n", + $mailHeaders ); + + // redirect to return URL + header( "Location: $return_url" ); + } + } + + + +/** + * Removes an account according to POSTed values. + */ +function sb_removeAccount() { + global $return_url, $tableNamePrefix; + + $user_id = sb_getRequestVariableSafe( "user_id" ); + + + $query = 0; + + global $header, $footer; + + $removalAllowed = true; + + if( $user_id == NULL ) { + + $removalAllowed = false; + + // display failure page + sb_messagePage( "No user_id field given." ); + } + else { + + if( !sb_isAdministrator() ) { + + $removalAllowed = false; + + // display failure page + sb_messagePage( "You must be an administrator to remove accounts." ); + } + else { + $query = "DELETE FROM $tableNamePrefix" . "users " . + "WHERE user_id = '$user_id';"; + } + } + + if( $removalAllowed ) { + sb_connectToDatabase(); + + sb_queryDatabase( $query ); + + sb_closeDatabase(); + + // redirect to return URL + header( "Location: $return_url" ); + } + } + + + +/** + * Displays the admin queue for a given blog. + * + * @param $inBlogName the name of the blog to show a queue for, or "*" to + * show queue for all blogs together. + */ +function sb_showPostQueue( $inBlogName ) { + global $header, $footer, $tableNamePrefix; + + if( ! sb_isAdministrator() ) { + sb_messagePage( "You must be an administrator to view the queue." ); + return; + } + + + $displayBlogName = ""; + $blogNameQueryLine = ""; + if( strcmp( $inBlogName, "*" ) != 0 ) { + $displayBlogName = "from $inBlogName "; + $blogNameQueryLine = "AND blog_name = '$inBlogName' "; + } + + include( $header ); + echo "
"; + + // get pending blog posts from the database + + $orderClause = "ORDER BY creation_date DESC"; + + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE approved = '0' AND removed = '0' ". + "$blogNameQueryLine". + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL ) " . + "$orderClause;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + + $numRows = mysql_numrows( $result ); + + global $currentColor, $altColor; + + /** + * Resets the value of the bg colors. + */ + function sb_resetBGColors() { + global $currentColor, $altColor; + + $currentColor = "#CCCCCC"; + $altColor = "#EEEEEE"; + } + + + /** + * Prints and alternating BGCOLOR attribute. + */ + function sb_printNextBGColor() { + global $currentColor, $altColor; + + echo "BGCOLOR=$currentColor"; + $tempColor = $currentColor; + $currentColor = $altColor; + $altColor = $tempColor; + } + + + if( $numRows == 0 ) { + echo ""; + } + else { + // table headers + echo ""; + echo ""; + echo ""; + echo ""; + } + + // this queue should be the return destination after edits + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + + for( $i=0; $i<$numRows; $i++ ) { + // restart color cycling + sb_resetBGColors(); + + $blog_name = mysql_result( $result, $i, "blog_name" ); + $subject_line = mysql_result( $result, $i, "subject_line" ); + $post_id = mysql_result( $result, $i, "post_id" ); + $author = mysql_result( $result, $i, "user_id" ); + + $context = $blog_name; + + if( preg_match( "/_comments/", $blog_name ) ) { + + preg_match( "/(.*)_comments/", $blog_name, $matches ); + + // matches[0] contains full matched string + // matches[1] contains first parenthesized subpattern + $parentPostID = $matches[1]; + + + // fetch subject line of parent post + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE post_id = '$parentPostID';"; + + $contextResult = sb_queryDatabase( $query ); + + $context_subject_line = + mysql_result( $contextResult, 0, "subject_line" ); + + $context = "Comment to ". + "$context_subject_line"; + } + + echo ""; + echo ""; + echo ""; + + echo ""; + + // blank space + echo ""; + } + + echo "
"; + echo "Posts $displayBlogName". + "waiting for approval:
[none]
Context:Author:Headline:
$context$author$subject_line[View]"; + + echo " - [". + "Edit]"; + + echo " - [". + "Approve]


"; + sb_closeDatabase(); + + include( $footer ); + } + + + +/** + * Displays the admin queue of pending account requests. + */ +function sb_showAccountQueue() { + global $header, $footer, $tableNamePrefix; + + if( ! sb_isAdministrator() ) { + sb_messagePage( "You must be an administrator to view the queue." ); + return; + } + + + include( $header ); + echo "
"; + + // get pending accounts from the database + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."users " . + "WHERE approved = '0';"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + global $currentColor, $altColor; + + /** + * Resets the value of the bg colors. + */ + function sb_resetBGColors() { + global $currentColor, $altColor; + + $currentColor = "#CCCCCC"; + $altColor = "#EEEEEE"; + } + + + /** + * Prints and alternating BGCOLOR attribute. + */ + function sb_printNextBGColor() { + global $currentColor, $altColor; + + echo "BGCOLOR=$currentColor"; + $tempColor = $currentColor; + $currentColor = $altColor; + $altColor = $tempColor; + } + + + if( $numRows == 0 ) { + echo ""; + } + else { + // table headers + echo ""; + echo ""; + echo ""; + } + + // this queue should be the return destination after edits + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + + for( $i=0; $i<$numRows; $i++ ) { + // restart color cycling + sb_resetBGColors(); + + $user_id = mysql_result( $result, $i, "user_id" ); + $email = mysql_result( $result, $i, "email" ); + + + echo ""; + echo ""; + + echo ""; + + // blank space + echo ""; + } + + echo "
"; + echo "Account requests ". + "waiting for approval:
[none]
User ID:Email:
$user_id$email[reject]"; + + echo " - [". + "approve]"; + + echo " - [". + "approve and make admin]


"; + + include( $footer ); + } + + + +/** + * Displays the admin list of all approved accounts in the system. + */ +function sb_showAccountList() { + global $header, $footer, $tableNamePrefix; + + if( ! sb_isAdministrator() ) { + sb_messagePage( "You must be an administrator to ". + "view the account list." ); + return; + } + + + include( $header ); + echo "
"; + + // get pending accounts from the database + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."users " . + "WHERE approved = '1' ". + "ORDER BY user_id ASC;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + global $currentColor, $altColor; + + /** + * Resets the value of the bg colors. + */ + function sb_resetBGColors() { + global $currentColor, $altColor; + + $currentColor = "#CCCCCC"; + $altColor = "#EEEEEE"; + } + + + /** + * Prints and alternating BGCOLOR attribute. + */ + function sb_printNextBGColor() { + global $currentColor, $altColor; + + echo "BGCOLOR=$currentColor"; + $tempColor = $currentColor; + $currentColor = $altColor; + $altColor = $tempColor; + } + + + if( $numRows == 0 ) { + echo ""; + } + else { + // table headers + echo ""; + echo ""; + echo ""; + echo ""; + } + + // this queue should be the return destination after edits + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + + for( $i=0; $i<$numRows; $i++ ) { + // restart color cycling + sb_resetBGColors(); + + $user_id = mysql_result( $result, $i, "user_id" ); + $email = mysql_result( $result, $i, "email" ); + $administrator = mysql_result( $result, $i, "administrator" ); + + + echo ""; + echo ""; + + if( $administrator ) { + echo ""; + } + else { + echo ""; + } + + echo ""; + } + + echo "
"; + echo "Active Accounts:
[none]
User ID:Email:Status:
$user_id$emailadmin[remove]"; + + if( $administrator == 1 ) { + echo " - [". + "revoke admin status]"; + } + else { + echo " - [". + "make admin]"; + } + + // blank space + echo "


"; + + include( $footer ); + } + + + +/** + * Performs search using posted variables and displays a results page. + */ +function sb_search() { + global $tableNamePrefix; + + $key_words = sb_getRequestVariableSafe( "key_words" ); + + // this result page should be the return destination after edits + $return_url = sb_getReturnURL(); + $return_url = urlencode( $return_url ); + + + $keywordArray = explode( " ", $key_words ); + $keywordWhereClause = ""; + + foreach( $keywordArray as $name => $word ) { + $keywordWhereClause = $keywordWhereClause . + "AND ( subject_line LIKE '%$word%' " . + "OR intro_text LIKE '%$word%' ". + "OR body_text LIKE '%$word%' )"; + } + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE approved = '1' AND removed = '0' ". + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL ) " . + "$keywordWhereClause;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + global $header, $footer; + + include( $header ); + + echo "
"; + + + echo "Search for $key_words:

"; + + if( $numRows == 0 ) { + echo "[no results]
"; + } + + global $storyBlockFormatOpen, $storyBlockFormatClose, + $headlineFormatOpen, $headlineFormatClose, + $textBlockFormatOpen, $textBlockFormatClose, $storySeparator; + + + for( $i=0; $i<$numRows; $i++ ) { + + $post_id = mysql_result( $result, $i, "post_id" ); + $blog_name = mysql_result( $result, $i, "blog_name" ); + $user_id = mysql_result( $result, $i, "user_id" ); + $subject_line = mysql_result( $result, $i, "subject_line" ); + $intro_text = mysql_result( $result, $i, "intro_text" ); + $body_text = mysql_result( $result, $i, "body_text" ); + $creation_date = mysql_result( $result, $i, "creation_date" ); + $allow_comments = mysql_result( $result, $i, "allow_comments" ); + $show_permalink = mysql_result( $result, $i, "show_permalink" ); + + sb_generateStoryBlock( $blog_name, + $post_id, + trim( $subject_line ), + $user_id, + $creation_date, + // hide up and down widgets + 0, + 0, + trim( $intro_text ), + trim( $body_text ), + 0, // show link to body text + $allow_comments, + $show_permalink, + $return_url, + // formatting options: + $storyBlockFormatOpen, + $storyBlockFormatClose, + $headlineFormatOpen, + $headlineFormatClose, + $textBlockFormatOpen, + $textBlockFormatClose ); + + if( $i < $numRows - 1 ) { + // separate from next story + echo "$storySeparator"; + } + } + + echo "
"; + include( $footer ); + } + + + +/** + * Generates HTML for a story block with intro text visible. + * + * The point of this function is to abstract out the basic story block + * rendering code so that seedBlogFormatted() and sb_search() can both use it. + * + * @param $inBlogName the name of the blog in the database. + * @param $inPostID the post ID. + * @param $inSubjectLine the whitespace trimmed subject line. + * @param $inUserID the author of the post, or NULL to hide the author byline. + * @param $inDateString the MySQL creation date string of this post, or NULL + * to hide the date from the display. + * @param $inShowUpWidget 1 to show up widgets for this post, or + * 0 to hide it. + * @param $inShowDownWidget 1 to show down widget for this post, or + * 0 to hide it. + * @param $inIntroText the raw intro text from the database, whitespace + * trimmed. + * @param $inBodyText the raw body text from the database, whitespace + * trimmed, or NULL if there is no body. + * @param $inEmbedBodyText 1 to include the body text in the story block, + * or 0 to show a "read more" link. + * @param $inAllowComments 1 to allow comments, or 0 to forbid them. + * @param $inShowPermalink 1 to show a permanent link, or 0 to hide it. + * @param $inReturnURL the URL of the page that this block is part of. + * + * Other parameters (formatting options) are identical to those passed into + * seedBlogFormatted. + */ +function sb_generateStoryBlock( $inBlogName, + $inPostID, + $inSubjectLine, + $inUserID, + $inDateString, + $inShowUpWidget, + $inShowDownWidget, + $inIntroText, + $inBodyText, + $inEmbedBodyText, + $inAllowComments, + $inShowPermalink, + $inReturnURL, + // formatting options: + $inStoryBlockFormatOpen, + $inStoryBlockFormatClose, + $inHeadlineFormatOpen, + $inHeadlineFormatClose, + $inTextBlockFormatOpen, + $inTextBlockFormatClose ) { + + // open story block + echo "$inStoryBlockFormatOpen\n"; + // formatted subject line (no link) + echo "$inHeadlineFormatOpen$inSubjectLine$inHeadlineFormatClose\n"; + echo "$inTextBlockFormatOpen"; + $show_author = 0; + $show_date = 0; + + if( $inUserID != NULL || $inDateString != NULL ) { + echo ""; + + if( $inUserID != NULL ) { + echo ""; + $show_author = 1; + } + if( $inDateString != NULL ) { + $timestamp = strtotime( $inDateString ); + // format as in Sunday, July 7, 2005 [4:52 pm] + $dateString = date( "l, F j, Y [g:i a]", $timestamp ); + + if( $inUserID == NULL ) { + echo ""; + + $show_date = 1; + } + echo "
by $inUserID"; + } + else { + echo ""; + } + echo "$dateString
"; + } + + if( sb_canEdit( $inPostID ) ) { + // Edit link next to subject + echo "[" . + "Edit]"; + + if( sb_isAdministrator() ) { + // show an approve link, if post is pending approval + + global $tableNamePrefix; + + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE post_id = '$inPostID';"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $approved = mysql_result( $result, 0, "approved" ); + + if( $approved == 0 ) { + echo "[". + "Approve]"; + } + } + + if( $inShowUpWidget ) { + echo "[" . + "Move Up]"; + } + if( $inShowDownWidget ) { + echo "[" . + "Move Down]"; + } + } + if( $inIntroText != NULL ) { + // intro text + $formattedIntro = sb_rcb_blog2html( $inIntroText ); + echo "
$formattedIntro"; + } + if( $inBodyText != NULL && $inEmbedBodyText ) { + $formattedBody = sb_rcb_blog2html( $inBodyText ); + echo "

$formattedBody"; + } + + // only open a table for the links if we are going to show some links + if( $inShowPermalink || + ( $inBodyText != NULL && ! $inEmbedBodyText ) || + $inAllowComments ) { + + // links under text + echo + "

"; + if( $inBodyText != NULL && ! $inEmbedBodyText ) { + // a read-more link + echo ""; + } + else if( $inShowPermalink ) { + // a perma link + echo ""; + } + if( $inAllowComments ) { + $approvedCount = sb_countComments( $inPostID, 1 ); + $queuedCount = sb_countComments( $inPostID, 0 ); + + $isAdmin = sb_isAdministrator(); + + echo ""; + } + + echo "
". + "Read more...". + "[Link]"; + + if( $approvedCount > 0 || + ( $isAdmin && $queuedCount > 0 ) ) { + echo "[". + "$approvedCount Comment"; + + if( $approvedCount != 1 ) { + echo "s"; + } + echo ""; + + if( $isAdmin && $queuedCount > 0 ) { + echo ", $queuedCount in Queue"; + } + echo "]"; + } + else { + // no comments yet, but show link for submission + + + $postLinkName = "Submit Comment"; + $allowPost = false; + + global $autoApprovePublicComments, $loggedInID; + + if( $autoApprovePublicComments || + strcmp( $loggedInID, "" ) != 0 ) { + + // post directly, don't submit + $postLinkName = "Post Comment"; + } + + echo "[" . + "$postLinkName]"; + + } + echo"
"; + } + + // close text block + echo "$inTextBlockFormatClose"; + + // close story block + echo "$inStoryBlockFormatClose"; + } + + + +/** + * Generates RSS 2.0 XML for a blog, using posted variables to select the + * blog and configure the RSS feed. + * + * The following RSS 2.0 spec was followed: + * http://blogs.law.harvard.edu/tech/rss + */ +function sb_rssFeed() { + global $tableNamePrefix; + + $blog_name = sb_getRequestVariableSafe( "blog_name" ); + $channel_title = + sb_stripMagicQuotes( sb_getRequestVariableSafe( "channel_title" ) ); + $channel_description = + sb_stripMagicQuotes( sb_getRequestVariableSafe( "channel_description" ) ); + $max_number = sb_getRequestVariableSafe( "max_number" ); + $show_authors = sb_getRequestVariableSafe( "show_authors" ); + $show_dates = sb_getRequestVariableSafe( "show_dates" ); + + + // for now, only order by creation date in RSS feed + $orderClause = "ORDER BY creation_date DESC"; + + + $limitNumber = $max_number; + + if( $max_number == -1 ) { + // use a large number, as suggested in the MySQL docs, to cause + // limit to be ignored + $limitNumber = 99999; + } + + + + // LIMIT is only supported by MySQL + $query = + "SELECT * " . + "FROM $tableNamePrefix"."posts " . + "WHERE approved = '1' AND removed = '0' ". + "AND blog_name = '$blog_name' ". + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL ) " . + "$orderClause LIMIT 0, $limitNumber;"; + + sb_connectToDatabase(); + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + $numRows = mysql_numrows( $result ); + + global $mainSiteURL, $fullSeedBlogsURL; + + header( "Content-type: application/xml" ); + + // echo this to avoid problems with \n"; + + // now inline the rest of the XML + + // tested this with a validator, and it is valid RSS +?> + + + + <?php echo $channel_title;?> + + + + + + <?php echo $subject_line;?> + + + + + + + + + +$inQueryString

" . + mysql_error() ); + + return $result; + } + + +/** + * Checks whether a table exists in the currently-connected database. + * + * @param $inTableName the name of the table to look for. + * + * @return 1 if the table exists, or 0 if not. + */ +function sb_doesTableExist( $inTableName ) { + // check if our table exists + $tableExists = 0; + + $query = "SHOW TABLES"; + $result = sb_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + + for( $i=0; $i<$numRows && ! $tableExists; $i++ ) { + + $tableName = mysql_result( $result, $i, 0 ); + + if( strcmp( $tableName, $inTableName ) == 0 ) { + $tableExists = 1; + } + } + return $tableExists; + } + + + +/** + * Displays the error page and dies. + * + * @param $message the error message to display on the error page. + */ +function sb_fatalError( $message ) { + //global $errorMessage; + + // set the variable that is displayed inside error.php + //$errorMessage = $message; + + //include_once( "error.php" ); + + // for now, just print error message + echo( "Fatal error: $message
" ); + die(); + } + + + +/** + * Displays a message page. + * + * @param $message the message to display. + */ +function sb_messagePage( $message ) { + global $header, $footer; + include( $header ); + echo( "
$message

" ); + include( $footer ); + } + + + +/** + * Prints form elements for selecting a date and time. + * + * @param $namePrefix the prefix to use in each form element + * name. For example, if $namePrefix is "my_", then the + * form elements will have the following names: + * my_month, my_day, my_year, my_hour, my_minute, my_ampm + * All fields have numerical posted values, except ampm, which + * is either "am" or "pm". + * @param $fillWithCurrentTime set to 1 to fill with current time. + * @param $selected____ indicates values that should be pre-selected. + */ +function sb_printDateTimeForm( $namePrefix, + $fillWithCurrentTime = 0, + $selectedMonth = NULL, $selectedDay = NULL, + $selectedYear = NULL, $selectedHour = NULL, + $selectedMinute = NULL, + $selectedAMPM = NULL ) { + + if( $fillWithCurrentTime ) { + $currentDateAndTime = getdate(); + + $selectedHour = $currentDateAndTime[ "hours" ]; + $selectedAMPM = "am"; + + if( $selectedHour > 11 ) { + + if( $selectedHour < 24 ) { + $selectedAMPM = "pm"; + } + $selectedHour = $selectedHour - 12; + } + + $selectedMonth = $currentDateAndTime[ "mon" ]; + $selectedDay = $currentDateAndTime[ "mday" ]; + $selectedYear = $currentDateAndTime[ "year" ]; + + $selectedMinute = $currentDateAndTime[ "minutes" ]; + } + + $months = array( "January", "February", "March", "April", + "May", "June", "July", "August", "September", + "October", "November", "December" ); + + echo ""; + echo "\n"; + + // start new line + echo ""; + + echo "
Date:\n"; + + echo "\n"; + + echo "
Time:\n"; + + echo "\n"; + + // radio for am/pm + $amCheckedState = ""; + $pmCheckedState = ""; + + if( strcmp( $selectedAMPM, "am" ) == 0 ) { + $amCheckedState = "checked"; + } + if( strcmp( $selectedAMPM, "pm" ) == 0 ) { + $pmCheckedState = "checked"; + } + + echo "am "; + echo "pm "; + + echo "
"; + } + + + +/** + * Prints form elements for selecting a date and time, preselecting a time + * using an SQL timestamp. + * + * @param $namePrefix the prefix to use in each form element + * name. For example, if $namePrefix is "my_", then the + * form elements will have the following names: + * my_month, my_day, my_year, my_hour, my_minute, my_ampm + * All fields have numerical posted values, except ampm, which + * is either "am" or "pm". + * @param $fillWithCurrentTime set to 1 to fill with current time. + * @param $selectedTimestamp the SQL timestamp to pre-select. + */ +function sb_printDateTimeFormFromTimestamp( $namePrefix, + $fillWithCurrentTime = 0, + $selectedTimestamp ) { + + if( $fillWithCurrentTime ) { + // ignore selectedTimestamp + sb_printDateTimeForm( $namePrefix, $fillWithCurrentTime ); + } + else { + $unixTimeInSeconds = strtotime( $selectedTimestamp ); + + // get array of separated time values + $timeValues = getdate( $unixTimeInSeconds ); + + $hours = $timeValues[ "hours" ]; + + // convert to 12-hour time + + $ampm = "am"; + + if( $hours > 11 ) { + + if( $hours < 24 ) { + $ampm = "pm"; + } + $hours = $hours - 12; + } + + sb_printDateTimeForm( $namePrefix, + $fillWithCurrentTime, + $timeValues[ "mon" ], $timeValues[ "mday" ], + $timeValues[ "year" ], + $hours, + $timeValues[ "minutes" ], + $ampm ); + } + } + + +/** + * Formats time data as an SQL timestamp. + * An example timestamp: "2005-01-19 17:22:50" + * + * Most parameters are self-explanatory, except: + * @param $ampm one of "am", "pm", or NULL to indicate 24-hour time. + */ +function sb_formatTime( $year, $month, $day, $hour, $minute, $second, $ampm ) { + + $formattedHour = $hour; + if( $ampm != NULL ) { + if( strcmp( $ampm, "pm" ) == 0) { + $formattedHour += 12; + } + } + if( $formattedHour < 10 ) { + $formattedHour = "0$formattedHour"; + } + + $formattedMinute = $minute; + if( $formattedMinute < 10 ) { + $formattedMinute = "0$formattedMinute"; + } + + $formattedSecond = $second; + if( $formattedSecond < 10 ) { + $formattedSecond = "0$formattedSecond"; + } + + $formattedDay = $day; + if( $formattedDay < 10 ) { + $formattedDay = "0$formattedDay"; + } + + $formattedMonth = $month; + if( $formattedMonth < 10 ) { + $formattedMonth = "0$formattedMonth"; + } + + + return "$year-$formattedMonth-$formattedDay " . + "$formattedHour:$formattedMinute:$formattedSecond"; + } + + + +/** + * Gets a post ID that is guaranteed to be unique. + * + * A user must be logged in for this to work properly. + * In other words, the global $loggedInID must be set. + * + * This function queries the database to ensure that the ID is actually + * unique and tries IDs until a uniqe one is found + * + * The correctness of this function depends on the fact that a given + * user will only be inserting one item at a time into the database. + * If multiple items are being inserted, each INSERT querie must be + * performed before the next getUniqueListingID call. + * + * @return a unique ID. + */ +function sb_getUniquePostID() { + global $loggedInID, $tableNamePrefix; + + // use current time as part of the ID string + $currentTime = time(); + + // keep trying until we create an ID that is unique in the database + // use counter in case more than one new item is inserted by + // a user in the same second (in which case, $currentTime will be + // the same for both items). + $uniqueListingCounter = 0; + + sb_connectToDatabase(); + + $foundUnique = 0; + $uniqueID = ""; + + while( ! $foundUnique ) { + + $uniqueID = "$loggedInID" . "_$currentTime" . "_$uniqueListingCounter"; + + $query = "SELECT * FROM $tableNamePrefix"."posts WHERE post_id = '$uniqueID';"; + + $result = sb_queryDatabase( $query ); + + $numRows = mysql_numrows( $result ); + + if( $numRows == 0 ) { + // found a unique ID + $foundUnique = 1; + } + else { + // collision with existing ID + + // increment counter and try again + $uniqueListingCounter ++; + } + } + sb_closeDatabase(); + + return $uniqueID; + } + + + +/** + * Computes cryptographic hash on a password. + * + * @param $user_id the user's ID. + * @param $password the user's password. + * + * @return the 32-character, hex-encoded MD5 hash. + */ +function sb_computePasswordHash( $user_id, $password ) { + global $siteShortName; + + $currentTime = time(); + + $stringToHash = "$siteShortName$user_id$password"; + $password_md5 = md5( $stringToHash ); + + return $password_md5; + } + + + +/** + * Computes a session ID for a user. + * + * @param $user_id the user's ID. + * @param $password the user's password. + * + * @return the 32-character session ID. + */ +function sb_computeSessionID( $user_id, $password ) { + global $siteShortName; + + $currentTime = time(); + + $session_id_string = "$siteShortName$user_id$password$currentTime"; + $session_id = md5( $session_id_string ); + + return $session_id; + } + + + +/** + * Refreshes a user's cookie. + * + * @param $user_id the user's ID. + * @param $session_id the session ID. + */ +function sb_refreshCookie( $user_id, $session_id ) { + global $cookieName; + + // expire in 24 hours + $expireTime = time() + 60 * 60 * 24; + + setcookie( $cookieName ."_user_id", sb_stripMagicQuotes( $user_id ), + $expireTime, "/" ); + setcookie( $cookieName ."_session_id", $session_id, $expireTime, "/" ); + } + + + +/** + * Clears a user's cookie. + * + */ +function sb_clearCookie() { + global $cookieName; + + // expire an hour ago + $expireTime = time() - 60 * 60; + + setcookie( $cookieName ."_user_id", "", $expireTime, "/" ); + setcookie( $cookieName ."_session_id", "", $expireTime, "/" ); + } + + + +/** + * Gets the ID of the user that is logged in. + * + * @return the user ID, or "" if no user is logged in. + */ +function sb_getLoggedInUser() { + global $cookieName; + + $cookie_user_id = ""; + if( isset( $_COOKIE[ $cookieName ."_user_id" ] ) ) { + $cookie_user_id = $_COOKIE[ $cookieName ."_user_id" ]; + } + $cookie_session_id = ""; + if( isset( $_COOKIE[ $cookieName ."_session_id" ] ) ) { + $cookie_session_id = $_COOKIE[ $cookieName ."_session_id" ]; + } + + global $justLoggedOut; + + if( ! $justLoggedOut && + strcmp( $cookie_user_id, "" ) != 0 && + strcmp( $cookie_session_id, "" ) != 0 && + // some versions of IE change cookie value to "deleted" upon deletion + // instead of clearing the cookie + strcmp( $cookie_user_id, "deleted" ) != 0 && + strcmp( $cookie_session_id, "deleted" ) != 0) { + + // check that session ID matches ID in database + $trueSessionID = sb_getUserDatabaseField( $cookie_user_id, "session_id" ); + + + // session ID in database is set and + // it matches the cookie session ID + if( strcmp( $trueSessionID, "" ) != 0 && + strcmp( $trueSessionID, $cookie_session_id ) == 0 ) { + + return $cookie_user_id; + } + } + + // else + return ""; + } + + + +/** + * Gets whether a user exists in the database. + * + * @param $user_id the user's ID. + */ +function sb_doesUserExist( $user_id ) { + global $tableNamePrefix; + sb_connectToDatabase(); + + $query = "SELECT * FROM $tableNamePrefix"."users " . + "WHERE user_id = '$user_id';"; + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + + $numRows = mysql_numrows( $result ); + + if( $numRows == 1 ) { + return 1; + } + else { + return 0; + } + } + + + +/** + * Gets whether the currently logged-in user has administrator status. + * + * @return true if user is an admin, or false otherwise. + */ +function sb_isAdministrator() { + + global $loggedInID; + if( strcmp( $loggedInID, "" ) == 0 ) { + // public can never edit + return false; + } + + if( sb_getUserDatabaseField( $loggedInID, "administrator" ) == 1 ) { + // admins can always edit + return true; + } + + return false; + } + + + +/** + * Gets the value of a user's field from the database. + * + * @param $user_id the user's ID. + * @param $fieldName the name of the field to get. + */ +function sb_getUserDatabaseField( $user_id, $fieldName ) { + global $tableNamePrefix; + + sb_connectToDatabase(); + + $query = "SELECT $fieldName FROM $tableNamePrefix"."users " . + "WHERE user_id = '$user_id';"; + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + + $numRows = mysql_numrows( $result ); + + if( $numRows == 1 ) { + + $fieldValue = mysql_result( $result, 0, $fieldName ); + return $fieldValue; + } + else { + sb_fatalError( + "Could not get database field $fieldName for user $user_id" ); + } + } + + +/** + * Gets the value of a post's field from the database. + * + * @param $post_id the post's ID. + * @param $fieldName the name of the field to get. + */ +function sb_getPostDatabaseField( $post_id, $fieldName ) { + global $tableNamePrefix; + + sb_connectToDatabase(); + + $query = "SELECT $fieldName FROM $tableNamePrefix"."posts " . + "WHERE post_id = '$post_id';"; + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + + + $numRows = mysql_numrows( $result ); + + if( $numRows == 1 ) { + + $fieldValue = mysql_result( $result, 0, $fieldName ); + return $fieldValue; + } + else { + sb_fatalError( + "Could not get database field $fieldName for post $post_id" ); + } + } + + + +/** + * Sets the value of a user's field in the database. + * + * @param $user_id the user's ID. + * @param $fieldName the name of the field to set. + * @param $fieldValue the value to set, or NULL to set the field to NULL. + * @param $autoQuote set to 1 to automatically add quotes to the fieldValue. + * Defaults to 1. + */ +function sb_setUserDatabaseField( $user_id, $fieldName, $fieldValue, + $autoQuote = 1 ) { + global $tableNamePrefix; + + sb_connectToDatabase(); + + $fieldData = $fieldValue; + + if( $autoQuote ) { + $fieldData = "'$fieldValue'"; + } + + if( $fieldValue == NULL ) { + $fieldData = "NULL"; + } + + $query = + "UPDATE $tableNamePrefix"."users SET $fieldName = $fieldData ". + "WHERE user_id = '$user_id';"; + + $result = sb_queryDatabase( $query ); + + sb_closeDatabase(); + } + + + +/** + * Strips any magically escaped quotes from a string. + * + * Deals with PHP magic quotes (either on or off) automatically in conjunction + * with this script's $use_magic_quotes variable. + * + * This function is useful for user-submitted strings that are *not* destined + * for the SQL database (for example, when setting cookies or displaying + * such strings to the user). + * + * @param the string to strip. + * + * @return the stripped string, with any escaped quotes fixed into normal + * quotes. + */ +function sb_stripMagicQuotes( $string ) { + global $use_magic_quotes; + if( $use_magic_quotes ) { + // magic quotes on + // need to strip slashes + return stripSlashes( $string ); + } + else { + // magic quotes off + // do nothing + return $string; + } + } + + + +/** + * Recursively applies the addslashes function to arrays of arrays. + * This effectively forces magic_quote escaping behavior, eliminating + * a slew of possible database security issues. + * + * @inValue the value or array to addslashes to. + * + * @return the value or array with slashes added. + */ +function sb_addslashes_deep( $inValue ) { + return + ( is_array( $inValue ) + ? array_map( 'sb_addslashes_deep', $inValue ) + : addslashes( $inValue ) ); + } + + + +/** + * Recursively applies the stripslashes function to arrays of arrays. + * This effectively disables magic_quote escaping behavior. + * + * @inValue the value or array to stripslashes from. + * + * @return the value or array with slashes removed. + */ +function sb_stripslashes_deep( $inValue ) { + return + ( is_array( $inValue ) + ? array_map( 'sb_stripslashes_deep', $inValue ) + : stripslashes( $inValue ) ); + } + + + +/** + * Gets the raw contents of a variable from the HTTP request. This will + * include escaped quotes if magic quotes are enabled. + * + * @param $inVariableName the name of the variable. + * + * @return the value of the variable. + */ +function sb_getRequestVariableRaw( $inVariableName ) { + return $_REQUEST[ $inVariableName ]; + } + + + +/** + * Gets the filtered of a variable from the HTTP request. This will + * include escaped quotes if magic quotes are enabled. + * Example filtering behavior: HTML tags are removed. + * + * @param $inVariableName the name of the variable. + * + * @return the filtered value of the variable. + */ +function sb_getRequestVariableSafe( $inVariableName ) { + if( isset( $_REQUEST[ $inVariableName ] ) ) { + return strip_tags( $_REQUEST[ $inVariableName ] ); + } + else { + return ""; + } + } + + + +/** + * Counts the number of users in the database. + * + * @return the number of users. + */ +function sb_getUserCount() { + global $tableNamePrefix; + + sb_connectToDatabase(); + $result = + sb_queryDatabase( "SELECT COUNT(*) FROM $tableNamePrefix"."users;" ); + $userCount = mysql_result( $result, 0, 0 ); + sb_closeDatabase(); + + return $userCount; + } + + + +/** + * Tests whether the currently logged-in user can edit a post. + * Works even if no user is logged in. + * + * @param $inPostID the post ID to test edit powers for. + * + * @return true if editing is allowed, or false if editing is forbidden. + */ +function sb_canEdit( $inPostID ) { + + global $loggedInID; + if( strcmp( $loggedInID, "" ) == 0 ) { + // public can never edit + return false; + } + + if( sb_getUserDatabaseField( $loggedInID, "administrator" ) == 1 ) { + // admins can always edit + return true; + } + + if( strcmp( $loggedInID, + sb_getPostDatabaseField( $inPostID, "user_id" ) ) == 0 ) { + // rest of users can only edit their own posts + return true; + } + else { + return false; + } + } + + + +/** + * Tests whether a post is visible (approved, not removed, and not expired). + * + * Must be connected to database before calling. + * + * @param $inPostID the ID to check. + * + * @return true if visible, or false if not. + */ +function sb_isPostVisible( $inPostID ) { + global $tableNamePrefix; + $query = + "SELECT COUNT(*) " . + "FROM $tableNamePrefix"."posts " . + "WHERE approved = '1' AND removed = '0' ". + "AND post_id = '$inPostID' ". + "AND ( expiration_date > CURRENT_TIMESTAMP OR " . + "expiration_date IS NULL );"; + + $result = sb_queryDatabase( $query ); + + if( mysql_result( $result, 0, 0 ) == 1 ) { + return true; + } + else { + return false; + } + } + + + +/** + * Gets the full URL that was called to invoke this script, including + * all GET query parameters. + * + * @return the full return URL. + */ +function sb_getReturnURL() { + $return_url = + "http://" . $_SERVER['HTTP_HOST'] . $_SERVER[ "SCRIPT_NAME" ]; + + $queryString = $_SERVER[ "QUERY_STRING" ]; + if( strcmp( $queryString, "" ) != 0 ) { + $return_url = $return_url . "?" . $_SERVER[ "QUERY_STRING" ]; + } + return $return_url; + } + + + +/** + * Strips HTML tags from data, preparing them for presentation as pure + * text in the browser. + * + * This function written by Noah Medling as part + * of RCBlog. + * + * @param $inData the data to strip. + * + * @return the stripped data. + */ +function sb_rcb_striphtml( $inData ){ + $patterns = array( '//', '/"/' ); + $replace = array( '<', '>', '"' ); + return preg_replace( $patterns, $replace, $inData ); + } + + + +/** + * Renders text containing BBCode as HTML for presentation in a browser. + * + * This function written by Noah Medling as part + * of RCBlog. + * + * @param $inData the data to convert. + * + * @return the stripped data. + */ +function sb_rcb_blog2html( $inData ){ + $patterns = array( + "@(\r\n|\r|\n)?\\[\\*\\](\r\n|\r|\n)?(.*?)(?=(\\[\\*\\])|(\\[/list\\]))@si", + + // [b][/b], [i][/i], [u][/u], [mono][/mono] + "@\\[b\\](.*?)\\[/b\\]@si", + "@\\[i\\](.*?)\\[/i\\]@si", + "@\\[u\\](.*?)\\[/u\\]@si", + "@\\[mono\\](.*?)\\[/mono\\]@si", + + // [color=][/color], [size=][/size] + "@\\[color=([^\\]\r\n]*)\\](.*?)\\[/color\\]@si", + "@\\[size=([0-9]+)\\](.*?)\\[/size\\]@si", + + // [quote=][/quote], [quote][/quote], [code][/code] + "@\\[quote="([^\r\n]*)"\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/quote\\](\r\n|\r|\n)?@si", + "@\\[quote\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/quote\\](\r\n|\r|\n)?@si", + "@\\[code\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/code\\](\r\n|\r|\n)?@si", + + // [center][/center], [right][/right], [justify][/justify], + // [centerblock][/centerblock] (centers a left-aligned block of text) + "@\\[center\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/center\\](\r\n|\r|\n)?@si", + "@\\[right\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/right\\](\r\n|\r|\n)?@si", + "@\\[justify\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/justify\\](\r\n|\r|\n)?@si", + "@\\[centerblock\\](\r\n|\r|\n)?(.*?)(\r\n|\r|\n)?\\[/centerblock\\](\r\n|\r|\n)?@si", + + // [list][*][/list], [list=][*][/list] + "@\\[list\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + "@\\[list=1\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + "@\\[list=a\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + "@\\[list=A\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + "@\\[list=i\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + "@\\[list=I\\](\r\n|\r|\n)*(.*?)(\r\n|\r|\n)*\\[/list\\](\r\n|\r|\n)?@si", + // "@(\r\n|\r|\n)?\\[\\*\\](\r\n|\r|\n)?([^\\[]*)@si", + + // [url=][/url], [url][/url], [email][/email] + "@\\[url=([^\\]\r\n]+)\\](.*?)\\[/url\\]@si", + "@\\[url\\](.*?)\\[/url\\]@si", + "@\\[urls=([^\\]\r\n]+)\\](.*?)\\[/urls\\]@si", + "@\\[urls\\](.*?)\\[/urls\\]@si", + "@\\[email\\](.*?)\\[/email\\]@si", + "@\\[a=([^\\]\r\n]+)\\]@si", + + // [img][/img], [img=][/img], [clear] + "@\\[img\\](.*?)\\[/img\\](\r\n|\r|\n)?@si", + "@\\[imgl\\](.*?)\\[/imgl\\](\r\n|\r|\n)?@si", + "@\\[imgr\\](.*?)\\[/imgr\\](\r\n|\r|\n)?@si", + "@\\[img=([^\\]\r\n]+)\\](.*?)\\[/img\\](\r\n|\r|\n)?@si", + "@\\[imgl=([^\\]\r\n]+)\\](.*?)\\[/imgl\\](\r\n|\r|\n)?@si", + "@\\[imgr=([^\\]\r\n]+)\\](.*?)\\[/imgr\\](\r\n|\r|\n)?@si", + "@\\[clear\\](\r\n|\r|\n)?@si", + + // [hr], \n + "@\\[hr\\](\r\n|\r|\n)?@si", + "@(\r\n|\r|\n)@"); + + $replace = array( + '
  • $3
  • ', + + // [b][/b], [i][/i], [u][/u], [mono][/mono] + '$1', + '$1', + '$1', + '$1', + + // [color=][/color], [size=][/size] + '$2', + '$2', + + // [quote][/quote], [code][/code] + '
    $1 wrote:

    $3
    ', + '
    $2
    ', + '
    $2
    ', + + // [center][/center], [right][/right], [justify][/justify], + // [centerblock][/centerblock] + '
    $2
    ', + '
    $2
    ', + '
    $2
    ', + '
    $2
    ', + + // [list][*][/list], [list=][*][/list] + '
      $2
    ', + '
      $2
    ', + '
      $2
    ', + '
      $2
    ', + '
      $2
    ', + '
      $2
    ', + // '
  • ', + + // [url=][/url], [url][/url], [email][/email] + '$2', + '$1', + '$2', + '$1', + '$1', + '', + + // [img][/img], [img=][/img], [clear] + '$1', + '$1', + '$1', + '$2', + '$2', + '$2', + '
    ', + + // [hr], \n + '
    ', + '
    '); + return preg_replace($patterns, $replace, sb_rcb_striphtml( $inData ) ); + } + + +?> diff --git a/documentation/html/slideShow/1.png b/documentation/html/slideShow/1.png new file mode 100644 index 0000000..16a70f4 Binary files /dev/null and b/documentation/html/slideShow/1.png differ diff --git a/documentation/html/slideShow/100.png b/documentation/html/slideShow/100.png new file mode 100644 index 0000000..e7f2ff5 Binary files /dev/null and b/documentation/html/slideShow/100.png differ diff --git a/documentation/html/slideShow/101.png b/documentation/html/slideShow/101.png new file mode 100644 index 0000000..9686d61 Binary files /dev/null and b/documentation/html/slideShow/101.png differ diff --git a/documentation/html/slideShow/102.png b/documentation/html/slideShow/102.png new file mode 100644 index 0000000..204a49f Binary files /dev/null and b/documentation/html/slideShow/102.png differ diff --git a/documentation/html/slideShow/103.png b/documentation/html/slideShow/103.png new file mode 100644 index 0000000..0e0bbf5 Binary files /dev/null and b/documentation/html/slideShow/103.png differ diff --git a/documentation/html/slideShow/104.png b/documentation/html/slideShow/104.png new file mode 100644 index 0000000..9be40f7 Binary files /dev/null and b/documentation/html/slideShow/104.png differ diff --git a/documentation/html/slideShow/105.png b/documentation/html/slideShow/105.png new file mode 100644 index 0000000..f0a1bb7 Binary files /dev/null and b/documentation/html/slideShow/105.png differ diff --git a/documentation/html/slideShow/106.png b/documentation/html/slideShow/106.png new file mode 100644 index 0000000..a8eefd5 Binary files /dev/null and b/documentation/html/slideShow/106.png differ diff --git a/documentation/html/slideShow/109.png b/documentation/html/slideShow/109.png new file mode 100644 index 0000000..3358944 Binary files /dev/null and b/documentation/html/slideShow/109.png differ diff --git a/documentation/html/slideShow/110.png b/documentation/html/slideShow/110.png new file mode 100644 index 0000000..a8ddc02 Binary files /dev/null and b/documentation/html/slideShow/110.png differ diff --git a/documentation/html/slideShow/113.png b/documentation/html/slideShow/113.png new file mode 100644 index 0000000..d903477 Binary files /dev/null and b/documentation/html/slideShow/113.png differ diff --git a/documentation/html/slideShow/114.png b/documentation/html/slideShow/114.png new file mode 100644 index 0000000..4c34a0d Binary files /dev/null and b/documentation/html/slideShow/114.png differ diff --git a/documentation/html/slideShow/117.png b/documentation/html/slideShow/117.png new file mode 100644 index 0000000..32b7a5a Binary files /dev/null and b/documentation/html/slideShow/117.png differ diff --git a/documentation/html/slideShow/118.png b/documentation/html/slideShow/118.png new file mode 100644 index 0000000..7c68f40 Binary files /dev/null and b/documentation/html/slideShow/118.png differ diff --git a/documentation/html/slideShow/121.png b/documentation/html/slideShow/121.png new file mode 100644 index 0000000..cd1e05a Binary files /dev/null and b/documentation/html/slideShow/121.png differ diff --git a/documentation/html/slideShow/122.png b/documentation/html/slideShow/122.png new file mode 100644 index 0000000..e67f599 Binary files /dev/null and b/documentation/html/slideShow/122.png differ diff --git a/documentation/html/slideShow/123.png b/documentation/html/slideShow/123.png new file mode 100644 index 0000000..a06723a Binary files /dev/null and b/documentation/html/slideShow/123.png differ diff --git a/documentation/html/slideShow/124.png b/documentation/html/slideShow/124.png new file mode 100644 index 0000000..25a5b13 Binary files /dev/null and b/documentation/html/slideShow/124.png differ diff --git a/documentation/html/slideShow/125.png b/documentation/html/slideShow/125.png new file mode 100644 index 0000000..f24dda8 Binary files /dev/null and b/documentation/html/slideShow/125.png differ diff --git a/documentation/html/slideShow/126.png b/documentation/html/slideShow/126.png new file mode 100644 index 0000000..0c90743 Binary files /dev/null and b/documentation/html/slideShow/126.png differ diff --git a/documentation/html/slideShow/127.png b/documentation/html/slideShow/127.png new file mode 100644 index 0000000..241a5e6 Binary files /dev/null and b/documentation/html/slideShow/127.png differ diff --git a/documentation/html/slideShow/128.png b/documentation/html/slideShow/128.png new file mode 100644 index 0000000..f9b7786 Binary files /dev/null and b/documentation/html/slideShow/128.png differ diff --git a/documentation/html/slideShow/129.png b/documentation/html/slideShow/129.png new file mode 100644 index 0000000..35642a5 Binary files /dev/null and b/documentation/html/slideShow/129.png differ diff --git a/documentation/html/slideShow/130.png b/documentation/html/slideShow/130.png new file mode 100644 index 0000000..abba89a Binary files /dev/null and b/documentation/html/slideShow/130.png differ diff --git a/documentation/html/slideShow/131.png b/documentation/html/slideShow/131.png new file mode 100644 index 0000000..f822d22 Binary files /dev/null and b/documentation/html/slideShow/131.png differ diff --git a/documentation/html/slideShow/132.png b/documentation/html/slideShow/132.png new file mode 100644 index 0000000..c182a0b Binary files /dev/null and b/documentation/html/slideShow/132.png differ diff --git a/documentation/html/slideShow/133.png b/documentation/html/slideShow/133.png new file mode 100644 index 0000000..e4f25ee Binary files /dev/null and b/documentation/html/slideShow/133.png differ diff --git a/documentation/html/slideShow/134.png b/documentation/html/slideShow/134.png new file mode 100644 index 0000000..937ffa1 Binary files /dev/null and b/documentation/html/slideShow/134.png differ diff --git a/documentation/html/slideShow/135.png b/documentation/html/slideShow/135.png new file mode 100644 index 0000000..c299283 Binary files /dev/null and b/documentation/html/slideShow/135.png differ diff --git a/documentation/html/slideShow/136.png b/documentation/html/slideShow/136.png new file mode 100644 index 0000000..ea4fe6c Binary files /dev/null and b/documentation/html/slideShow/136.png differ diff --git a/documentation/html/slideShow/137.png b/documentation/html/slideShow/137.png new file mode 100644 index 0000000..ff453c0 Binary files /dev/null and b/documentation/html/slideShow/137.png differ diff --git a/documentation/html/slideShow/138.png b/documentation/html/slideShow/138.png new file mode 100644 index 0000000..2c6347a Binary files /dev/null and b/documentation/html/slideShow/138.png differ diff --git a/documentation/html/slideShow/139.png b/documentation/html/slideShow/139.png new file mode 100644 index 0000000..f013720 Binary files /dev/null and b/documentation/html/slideShow/139.png differ diff --git a/documentation/html/slideShow/14.png b/documentation/html/slideShow/14.png new file mode 100644 index 0000000..d7c0a9b Binary files /dev/null and b/documentation/html/slideShow/14.png differ diff --git a/documentation/html/slideShow/140.png b/documentation/html/slideShow/140.png new file mode 100644 index 0000000..78ac422 Binary files /dev/null and b/documentation/html/slideShow/140.png differ diff --git a/documentation/html/slideShow/141.png b/documentation/html/slideShow/141.png new file mode 100644 index 0000000..101eeda Binary files /dev/null and b/documentation/html/slideShow/141.png differ diff --git a/documentation/html/slideShow/142.png b/documentation/html/slideShow/142.png new file mode 100644 index 0000000..d0093f5 Binary files /dev/null and b/documentation/html/slideShow/142.png differ diff --git a/documentation/html/slideShow/143.png b/documentation/html/slideShow/143.png new file mode 100644 index 0000000..c01187f Binary files /dev/null and b/documentation/html/slideShow/143.png differ diff --git a/documentation/html/slideShow/144.png b/documentation/html/slideShow/144.png new file mode 100644 index 0000000..0c8694d Binary files /dev/null and b/documentation/html/slideShow/144.png differ diff --git a/documentation/html/slideShow/145.png b/documentation/html/slideShow/145.png new file mode 100644 index 0000000..2262160 Binary files /dev/null and b/documentation/html/slideShow/145.png differ diff --git a/documentation/html/slideShow/146.png b/documentation/html/slideShow/146.png new file mode 100644 index 0000000..72ef660 Binary files /dev/null and b/documentation/html/slideShow/146.png differ diff --git a/documentation/html/slideShow/148.png b/documentation/html/slideShow/148.png new file mode 100644 index 0000000..4a89b41 Binary files /dev/null and b/documentation/html/slideShow/148.png differ diff --git a/documentation/html/slideShow/17.png b/documentation/html/slideShow/17.png new file mode 100644 index 0000000..6d4de71 Binary files /dev/null and b/documentation/html/slideShow/17.png differ diff --git a/documentation/html/slideShow/18.png b/documentation/html/slideShow/18.png new file mode 100644 index 0000000..75da98e Binary files /dev/null and b/documentation/html/slideShow/18.png differ diff --git a/documentation/html/slideShow/19.png b/documentation/html/slideShow/19.png new file mode 100644 index 0000000..593c971 Binary files /dev/null and b/documentation/html/slideShow/19.png differ diff --git a/documentation/html/slideShow/20.png b/documentation/html/slideShow/20.png new file mode 100644 index 0000000..1af8c5c Binary files /dev/null and b/documentation/html/slideShow/20.png differ diff --git a/documentation/html/slideShow/21.png b/documentation/html/slideShow/21.png new file mode 100644 index 0000000..ab1b870 Binary files /dev/null and b/documentation/html/slideShow/21.png differ diff --git a/documentation/html/slideShow/22.png b/documentation/html/slideShow/22.png new file mode 100644 index 0000000..834c18d Binary files /dev/null and b/documentation/html/slideShow/22.png differ diff --git a/documentation/html/slideShow/23.png b/documentation/html/slideShow/23.png new file mode 100644 index 0000000..9360640 Binary files /dev/null and b/documentation/html/slideShow/23.png differ diff --git a/documentation/html/slideShow/24.png b/documentation/html/slideShow/24.png new file mode 100644 index 0000000..6e50abc Binary files /dev/null and b/documentation/html/slideShow/24.png differ diff --git a/documentation/html/slideShow/25.png b/documentation/html/slideShow/25.png new file mode 100644 index 0000000..0f2999e Binary files /dev/null and b/documentation/html/slideShow/25.png differ diff --git a/documentation/html/slideShow/26.png b/documentation/html/slideShow/26.png new file mode 100644 index 0000000..d17f179 Binary files /dev/null and b/documentation/html/slideShow/26.png differ diff --git a/documentation/html/slideShow/29.png b/documentation/html/slideShow/29.png new file mode 100644 index 0000000..7d3a4f6 Binary files /dev/null and b/documentation/html/slideShow/29.png differ diff --git a/documentation/html/slideShow/3.png b/documentation/html/slideShow/3.png new file mode 100644 index 0000000..3c82aa8 Binary files /dev/null and b/documentation/html/slideShow/3.png differ diff --git a/documentation/html/slideShow/30.png b/documentation/html/slideShow/30.png new file mode 100644 index 0000000..29ccc08 Binary files /dev/null and b/documentation/html/slideShow/30.png differ diff --git a/documentation/html/slideShow/31.png b/documentation/html/slideShow/31.png new file mode 100644 index 0000000..c9c389a Binary files /dev/null and b/documentation/html/slideShow/31.png differ diff --git a/documentation/html/slideShow/32.png b/documentation/html/slideShow/32.png new file mode 100644 index 0000000..97351dd Binary files /dev/null and b/documentation/html/slideShow/32.png differ diff --git a/documentation/html/slideShow/33.png b/documentation/html/slideShow/33.png new file mode 100644 index 0000000..3c10c14 Binary files /dev/null and b/documentation/html/slideShow/33.png differ diff --git a/documentation/html/slideShow/34.png b/documentation/html/slideShow/34.png new file mode 100644 index 0000000..bdfb996 Binary files /dev/null and b/documentation/html/slideShow/34.png differ diff --git a/documentation/html/slideShow/35.png b/documentation/html/slideShow/35.png new file mode 100644 index 0000000..5d0a42d Binary files /dev/null and b/documentation/html/slideShow/35.png differ diff --git a/documentation/html/slideShow/351.png b/documentation/html/slideShow/351.png new file mode 100644 index 0000000..6d3aabb Binary files /dev/null and b/documentation/html/slideShow/351.png differ diff --git a/documentation/html/slideShow/36.png b/documentation/html/slideShow/36.png new file mode 100644 index 0000000..e711aee Binary files /dev/null and b/documentation/html/slideShow/36.png differ diff --git a/documentation/html/slideShow/37.png b/documentation/html/slideShow/37.png new file mode 100644 index 0000000..cadb602 Binary files /dev/null and b/documentation/html/slideShow/37.png differ diff --git a/documentation/html/slideShow/38.png b/documentation/html/slideShow/38.png new file mode 100644 index 0000000..4c9801b Binary files /dev/null and b/documentation/html/slideShow/38.png differ diff --git a/documentation/html/slideShow/39.png b/documentation/html/slideShow/39.png new file mode 100644 index 0000000..ce65e73 Binary files /dev/null and b/documentation/html/slideShow/39.png differ diff --git a/documentation/html/slideShow/4.png b/documentation/html/slideShow/4.png new file mode 100644 index 0000000..4fa32fc Binary files /dev/null and b/documentation/html/slideShow/4.png differ diff --git a/documentation/html/slideShow/40.png b/documentation/html/slideShow/40.png new file mode 100644 index 0000000..44f33b1 Binary files /dev/null and b/documentation/html/slideShow/40.png differ diff --git a/documentation/html/slideShow/41.png b/documentation/html/slideShow/41.png new file mode 100644 index 0000000..c5f1cb0 Binary files /dev/null and b/documentation/html/slideShow/41.png differ diff --git a/documentation/html/slideShow/42.png b/documentation/html/slideShow/42.png new file mode 100644 index 0000000..2471e97 Binary files /dev/null and b/documentation/html/slideShow/42.png differ diff --git a/documentation/html/slideShow/43.png b/documentation/html/slideShow/43.png new file mode 100644 index 0000000..e7774e6 Binary files /dev/null and b/documentation/html/slideShow/43.png differ diff --git a/documentation/html/slideShow/44.png b/documentation/html/slideShow/44.png new file mode 100644 index 0000000..077a4ef Binary files /dev/null and b/documentation/html/slideShow/44.png differ diff --git a/documentation/html/slideShow/45.png b/documentation/html/slideShow/45.png new file mode 100644 index 0000000..b9ad025 Binary files /dev/null and b/documentation/html/slideShow/45.png differ diff --git a/documentation/html/slideShow/48.png b/documentation/html/slideShow/48.png new file mode 100644 index 0000000..8e168c4 Binary files /dev/null and b/documentation/html/slideShow/48.png differ diff --git a/documentation/html/slideShow/49.png b/documentation/html/slideShow/49.png new file mode 100644 index 0000000..733734b Binary files /dev/null and b/documentation/html/slideShow/49.png differ diff --git a/documentation/html/slideShow/50.png b/documentation/html/slideShow/50.png new file mode 100644 index 0000000..3242c99 Binary files /dev/null and b/documentation/html/slideShow/50.png differ diff --git a/documentation/html/slideShow/55.png b/documentation/html/slideShow/55.png new file mode 100644 index 0000000..cf40f68 Binary files /dev/null and b/documentation/html/slideShow/55.png differ diff --git a/documentation/html/slideShow/56.png b/documentation/html/slideShow/56.png new file mode 100644 index 0000000..4194836 Binary files /dev/null and b/documentation/html/slideShow/56.png differ diff --git a/documentation/html/slideShow/59.png b/documentation/html/slideShow/59.png new file mode 100644 index 0000000..09c19eb Binary files /dev/null and b/documentation/html/slideShow/59.png differ diff --git a/documentation/html/slideShow/60.png b/documentation/html/slideShow/60.png new file mode 100644 index 0000000..3e3c069 Binary files /dev/null and b/documentation/html/slideShow/60.png differ diff --git a/documentation/html/slideShow/61.png b/documentation/html/slideShow/61.png new file mode 100644 index 0000000..ccbdf65 Binary files /dev/null and b/documentation/html/slideShow/61.png differ diff --git a/documentation/html/slideShow/62.png b/documentation/html/slideShow/62.png new file mode 100644 index 0000000..be522b1 Binary files /dev/null and b/documentation/html/slideShow/62.png differ diff --git a/documentation/html/slideShow/63.png b/documentation/html/slideShow/63.png new file mode 100644 index 0000000..071a930 Binary files /dev/null and b/documentation/html/slideShow/63.png differ diff --git a/documentation/html/slideShow/64.png b/documentation/html/slideShow/64.png new file mode 100644 index 0000000..5ef37b5 Binary files /dev/null and b/documentation/html/slideShow/64.png differ diff --git a/documentation/html/slideShow/69.png b/documentation/html/slideShow/69.png new file mode 100644 index 0000000..62e9010 Binary files /dev/null and b/documentation/html/slideShow/69.png differ diff --git a/documentation/html/slideShow/7.png b/documentation/html/slideShow/7.png new file mode 100644 index 0000000..2e14ef9 Binary files /dev/null and b/documentation/html/slideShow/7.png differ diff --git a/documentation/html/slideShow/70.png b/documentation/html/slideShow/70.png new file mode 100644 index 0000000..3afd741 Binary files /dev/null and b/documentation/html/slideShow/70.png differ diff --git a/documentation/html/slideShow/71.png b/documentation/html/slideShow/71.png new file mode 100644 index 0000000..da8b456 Binary files /dev/null and b/documentation/html/slideShow/71.png differ diff --git a/documentation/html/slideShow/72.png b/documentation/html/slideShow/72.png new file mode 100644 index 0000000..56e93a1 Binary files /dev/null and b/documentation/html/slideShow/72.png differ diff --git a/documentation/html/slideShow/73.png b/documentation/html/slideShow/73.png new file mode 100644 index 0000000..1e033f1 Binary files /dev/null and b/documentation/html/slideShow/73.png differ diff --git a/documentation/html/slideShow/74.png b/documentation/html/slideShow/74.png new file mode 100644 index 0000000..652163b Binary files /dev/null and b/documentation/html/slideShow/74.png differ diff --git a/documentation/html/slideShow/75.png b/documentation/html/slideShow/75.png new file mode 100644 index 0000000..a539278 Binary files /dev/null and b/documentation/html/slideShow/75.png differ diff --git a/documentation/html/slideShow/76.png b/documentation/html/slideShow/76.png new file mode 100644 index 0000000..8b16bd0 Binary files /dev/null and b/documentation/html/slideShow/76.png differ diff --git a/documentation/html/slideShow/77.png b/documentation/html/slideShow/77.png new file mode 100644 index 0000000..c001a43 Binary files /dev/null and b/documentation/html/slideShow/77.png differ diff --git a/documentation/html/slideShow/78.png b/documentation/html/slideShow/78.png new file mode 100644 index 0000000..bce4c09 Binary files /dev/null and b/documentation/html/slideShow/78.png differ diff --git a/documentation/html/slideShow/79.png b/documentation/html/slideShow/79.png new file mode 100644 index 0000000..cd35732 Binary files /dev/null and b/documentation/html/slideShow/79.png differ diff --git a/documentation/html/slideShow/8.png b/documentation/html/slideShow/8.png new file mode 100644 index 0000000..c754b4d Binary files /dev/null and b/documentation/html/slideShow/8.png differ diff --git a/documentation/html/slideShow/80.png b/documentation/html/slideShow/80.png new file mode 100644 index 0000000..3fdc06f Binary files /dev/null and b/documentation/html/slideShow/80.png differ diff --git a/documentation/html/slideShow/81.png b/documentation/html/slideShow/81.png new file mode 100644 index 0000000..019f1a0 Binary files /dev/null and b/documentation/html/slideShow/81.png differ diff --git a/documentation/html/slideShow/84.png b/documentation/html/slideShow/84.png new file mode 100644 index 0000000..34e32c2 Binary files /dev/null and b/documentation/html/slideShow/84.png differ diff --git a/documentation/html/slideShow/85.png b/documentation/html/slideShow/85.png new file mode 100644 index 0000000..7da2e5e Binary files /dev/null and b/documentation/html/slideShow/85.png differ diff --git a/documentation/html/slideShow/86.png b/documentation/html/slideShow/86.png new file mode 100644 index 0000000..d529ede Binary files /dev/null and b/documentation/html/slideShow/86.png differ diff --git a/documentation/html/slideShow/87.png b/documentation/html/slideShow/87.png new file mode 100644 index 0000000..2e1f030 Binary files /dev/null and b/documentation/html/slideShow/87.png differ diff --git a/documentation/html/slideShow/88.png b/documentation/html/slideShow/88.png new file mode 100644 index 0000000..524bbbc Binary files /dev/null and b/documentation/html/slideShow/88.png differ diff --git a/documentation/html/slideShow/91.png b/documentation/html/slideShow/91.png new file mode 100644 index 0000000..3605a9c Binary files /dev/null and b/documentation/html/slideShow/91.png differ diff --git a/documentation/html/slideShow/92.png b/documentation/html/slideShow/92.png new file mode 100644 index 0000000..8032c85 Binary files /dev/null and b/documentation/html/slideShow/92.png differ diff --git a/documentation/html/slideShow/93.png b/documentation/html/slideShow/93.png new file mode 100644 index 0000000..48ac80e Binary files /dev/null and b/documentation/html/slideShow/93.png differ diff --git a/documentation/html/slideShow/94.png b/documentation/html/slideShow/94.png new file mode 100644 index 0000000..eccf119 Binary files /dev/null and b/documentation/html/slideShow/94.png differ diff --git a/documentation/html/slideShow/97.png b/documentation/html/slideShow/97.png new file mode 100644 index 0000000..5e8a606 Binary files /dev/null and b/documentation/html/slideShow/97.png differ diff --git a/documentation/html/slideShow/98.png b/documentation/html/slideShow/98.png new file mode 100644 index 0000000..c3d3946 Binary files /dev/null and b/documentation/html/slideShow/98.png differ diff --git a/documentation/html/slideShow/99.png b/documentation/html/slideShow/99.png new file mode 100644 index 0000000..6d047b2 Binary files /dev/null and b/documentation/html/slideShow/99.png differ diff --git a/documentation/html/slideShow/actualScreenshot.png b/documentation/html/slideShow/actualScreenshot.png new file mode 100644 index 0000000..2f01c98 Binary files /dev/null and b/documentation/html/slideShow/actualScreenshot.png differ diff --git a/documentation/html/slideShow/footer.php b/documentation/html/slideShow/footer.php new file mode 100644 index 0000000..2c8d8ec --- /dev/null +++ b/documentation/html/slideShow/footer.php @@ -0,0 +1,23 @@ +
  • + +"; + + + + + + +echo ""; + + +?> + + + + + \ No newline at end of file diff --git a/documentation/html/slideShow/frameList.php b/documentation/html/slideShow/frameList.php new file mode 100644 index 0000000..8e64427 --- /dev/null +++ b/documentation/html/slideShow/frameList.php @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/documentation/html/slideShow/header.php b/documentation/html/slideShow/header.php new file mode 100644 index 0000000..b23cb2a --- /dev/null +++ b/documentation/html/slideShow/header.php @@ -0,0 +1,12 @@ + + + +Sleep Is Death Flip Book + + + + + +[home] [order] [start] +
    \ No newline at end of file diff --git a/documentation/html/slideShow/index.php b/documentation/html/slideShow/index.php new file mode 100644 index 0000000..73b2ee1 --- /dev/null +++ b/documentation/html/slideShow/index.php @@ -0,0 +1,133 @@ +"; + +// clicking on image goes to next +if( $next != -1 ) { + echo ""; + } +echo ""; +if( $next != -1 ) { + echo ""; + } + +echo ""; + + +echo ""; + +echo ""; +if( $prev != -1 ) { + echo "". + ""; + } +else { + echo ""; + } +echo ""; + +echo ""; + +echo ""; + +echo ""; + + +echo ""; +if( $next != -1 ) { + echo "". + ""; + } +else { + echo ""; + } + +echo ""; + +echo ""; + + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + +if( $next != -1 ) { + echo ""; + } + + + +include( "footer.php" ); + +?> \ No newline at end of file diff --git a/documentation/html/slideShow/next.png b/documentation/html/slideShow/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/slideShow/next.png differ diff --git a/documentation/html/slideShow/noArrow.png b/documentation/html/slideShow/noArrow.png new file mode 100644 index 0000000..3e7a163 Binary files /dev/null and b/documentation/html/slideShow/noArrow.png differ diff --git a/documentation/html/slideShow/prev.png b/documentation/html/slideShow/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/documentation/html/slideShow/prev.png differ diff --git a/documentation/html/slideShowLead.png b/documentation/html/slideShowLead.png new file mode 100644 index 0000000..5b10eb5 Binary files /dev/null and b/documentation/html/slideShowLead.png differ diff --git a/documentation/html/smb.png b/documentation/html/smb.png new file mode 100644 index 0000000..0d4ce5e Binary files /dev/null and b/documentation/html/smb.png differ diff --git a/documentation/html/stories.php b/documentation/html/stories.php new file mode 100644 index 0000000..28bc0a8 --- /dev/null +++ b/documentation/html/stories.php @@ -0,0 +1,36 @@ + + +Interesting Stories +
    +Click the images below to view each flip book.
    +
    + +A resource pack is available for each story, free to everyone who bought download access to the game. +
    +
    +
    + + +
    +
    +Pareidolia, by Jonathan D'Amato (played by Charles D'Amato) +
    + +
    +
    + +
    +
    +The Other Orchard, by Gordon Levine (played by Maggie Gosselar) +
    + + +
    +
    +Grandma's Photo, by Patrick Rodriguez (played by Adalberto Garza) +
    + + + + + diff --git a/documentation/html/stories/GrandmasPhoto.png b/documentation/html/stories/GrandmasPhoto.png new file mode 100644 index 0000000..f35409f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto.png differ diff --git a/documentation/html/stories/GrandmasPhoto/bakIndex.html b/documentation/html/stories/GrandmasPhoto/bakIndex.html new file mode 100644 index 0000000..86ff9eb --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/bakIndex.html @@ -0,0 +1,81 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/footer.php b/documentation/html/stories/GrandmasPhoto/footer.php new file mode 100644 index 0000000..1c02674 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/footer.php @@ -0,0 +1,9 @@ +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/frameList.php b/documentation/html/stories/GrandmasPhoto/frameList.php new file mode 100644 index 0000000..f6b0ed8 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/frameList.php @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/header.php b/documentation/html/stories/GrandmasPhoto/header.php new file mode 100644 index 0000000..9ebebd9 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/header.php @@ -0,0 +1,13 @@ + + + +Sleep Is Death Flip Book + + + + + +[home] [order] [start] + +
    \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00001.html b/documentation/html/stories/GrandmasPhoto/images/00001.html new file mode 100644 index 0000000..6d0ada6 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00001.html @@ -0,0 +1,87 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + +
    + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00001.png b/documentation/html/stories/GrandmasPhoto/images/00001.png new file mode 100644 index 0000000..249df41 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00001.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00002.html b/documentation/html/stories/GrandmasPhoto/images/00002.html new file mode 100644 index 0000000..143bd50 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00002.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00002.png b/documentation/html/stories/GrandmasPhoto/images/00002.png new file mode 100644 index 0000000..5938467 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00002.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00003.html b/documentation/html/stories/GrandmasPhoto/images/00003.html new file mode 100644 index 0000000..a1ecc19 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00003.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00003.png b/documentation/html/stories/GrandmasPhoto/images/00003.png new file mode 100644 index 0000000..1bb6f7a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00003.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00004.html b/documentation/html/stories/GrandmasPhoto/images/00004.html new file mode 100644 index 0000000..2b21e71 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00004.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00004.png b/documentation/html/stories/GrandmasPhoto/images/00004.png new file mode 100644 index 0000000..4b5b024 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00004.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00005.html b/documentation/html/stories/GrandmasPhoto/images/00005.html new file mode 100644 index 0000000..50fb37d --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00005.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00005.png b/documentation/html/stories/GrandmasPhoto/images/00005.png new file mode 100644 index 0000000..5933402 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00005.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00006.html b/documentation/html/stories/GrandmasPhoto/images/00006.html new file mode 100644 index 0000000..e938ae9 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00006.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00006.png b/documentation/html/stories/GrandmasPhoto/images/00006.png new file mode 100644 index 0000000..1daef0b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00006.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00007.html b/documentation/html/stories/GrandmasPhoto/images/00007.html new file mode 100644 index 0000000..972409b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00007.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00007.png b/documentation/html/stories/GrandmasPhoto/images/00007.png new file mode 100644 index 0000000..4131968 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00007.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00008.html b/documentation/html/stories/GrandmasPhoto/images/00008.html new file mode 100644 index 0000000..a8b8d20 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00008.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00008.png b/documentation/html/stories/GrandmasPhoto/images/00008.png new file mode 100644 index 0000000..a447739 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00008.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00009.html b/documentation/html/stories/GrandmasPhoto/images/00009.html new file mode 100644 index 0000000..ed49def --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00009.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00009.png b/documentation/html/stories/GrandmasPhoto/images/00009.png new file mode 100644 index 0000000..e166de3 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00009.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00010.html b/documentation/html/stories/GrandmasPhoto/images/00010.html new file mode 100644 index 0000000..0a2179b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00010.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00010.png b/documentation/html/stories/GrandmasPhoto/images/00010.png new file mode 100644 index 0000000..f35409f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00010.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00011.html b/documentation/html/stories/GrandmasPhoto/images/00011.html new file mode 100644 index 0000000..a43d31f --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00011.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00011.png b/documentation/html/stories/GrandmasPhoto/images/00011.png new file mode 100644 index 0000000..508be75 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00011.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00012.html b/documentation/html/stories/GrandmasPhoto/images/00012.html new file mode 100644 index 0000000..34d4e7c --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00012.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00012.png b/documentation/html/stories/GrandmasPhoto/images/00012.png new file mode 100644 index 0000000..2a663b3 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00012.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00013.html b/documentation/html/stories/GrandmasPhoto/images/00013.html new file mode 100644 index 0000000..fc095d9 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00013.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00013.png b/documentation/html/stories/GrandmasPhoto/images/00013.png new file mode 100644 index 0000000..7f4c77e Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00013.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00014.html b/documentation/html/stories/GrandmasPhoto/images/00014.html new file mode 100644 index 0000000..bbfd475 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00014.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00014.png b/documentation/html/stories/GrandmasPhoto/images/00014.png new file mode 100644 index 0000000..060b907 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00014.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00015.html b/documentation/html/stories/GrandmasPhoto/images/00015.html new file mode 100644 index 0000000..3aecee0 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00015.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00015.png b/documentation/html/stories/GrandmasPhoto/images/00015.png new file mode 100644 index 0000000..7620084 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00015.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00016.html b/documentation/html/stories/GrandmasPhoto/images/00016.html new file mode 100644 index 0000000..3f9368f --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00016.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00016.png b/documentation/html/stories/GrandmasPhoto/images/00016.png new file mode 100644 index 0000000..30fb8f2 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00016.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00017.html b/documentation/html/stories/GrandmasPhoto/images/00017.html new file mode 100644 index 0000000..db115c5 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00017.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00017.png b/documentation/html/stories/GrandmasPhoto/images/00017.png new file mode 100644 index 0000000..c2d6305 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00017.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00018.html b/documentation/html/stories/GrandmasPhoto/images/00018.html new file mode 100644 index 0000000..d263f96 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00018.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00018.png b/documentation/html/stories/GrandmasPhoto/images/00018.png new file mode 100644 index 0000000..554cfa7 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00018.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00019.html b/documentation/html/stories/GrandmasPhoto/images/00019.html new file mode 100644 index 0000000..d0ee17b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00019.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00019.png b/documentation/html/stories/GrandmasPhoto/images/00019.png new file mode 100644 index 0000000..99d9a0f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00019.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00020.html b/documentation/html/stories/GrandmasPhoto/images/00020.html new file mode 100644 index 0000000..dd84502 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00020.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00020.png b/documentation/html/stories/GrandmasPhoto/images/00020.png new file mode 100644 index 0000000..7ac54e0 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00020.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00021.html b/documentation/html/stories/GrandmasPhoto/images/00021.html new file mode 100644 index 0000000..bcc5c25 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00021.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00021.png b/documentation/html/stories/GrandmasPhoto/images/00021.png new file mode 100644 index 0000000..41b3625 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00021.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00022.html b/documentation/html/stories/GrandmasPhoto/images/00022.html new file mode 100644 index 0000000..f4aea77 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00022.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00022.png b/documentation/html/stories/GrandmasPhoto/images/00022.png new file mode 100644 index 0000000..436d40b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00022.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00023.html b/documentation/html/stories/GrandmasPhoto/images/00023.html new file mode 100644 index 0000000..eff5548 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00023.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00023.png b/documentation/html/stories/GrandmasPhoto/images/00023.png new file mode 100644 index 0000000..28a67ab Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00023.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00024.html b/documentation/html/stories/GrandmasPhoto/images/00024.html new file mode 100644 index 0000000..910351b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00024.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00024.png b/documentation/html/stories/GrandmasPhoto/images/00024.png new file mode 100644 index 0000000..b65e42d Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00024.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00025.html b/documentation/html/stories/GrandmasPhoto/images/00025.html new file mode 100644 index 0000000..a384d8f --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00025.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00025.png b/documentation/html/stories/GrandmasPhoto/images/00025.png new file mode 100644 index 0000000..5a1367f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00025.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00026.html b/documentation/html/stories/GrandmasPhoto/images/00026.html new file mode 100644 index 0000000..3e2ef4c --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00026.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00026.png b/documentation/html/stories/GrandmasPhoto/images/00026.png new file mode 100644 index 0000000..ab72dd7 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00026.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00027.html b/documentation/html/stories/GrandmasPhoto/images/00027.html new file mode 100644 index 0000000..ac8daaf --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00027.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00027.png b/documentation/html/stories/GrandmasPhoto/images/00027.png new file mode 100644 index 0000000..f5b30a6 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00027.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00028.html b/documentation/html/stories/GrandmasPhoto/images/00028.html new file mode 100644 index 0000000..c4fdfdb --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00028.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00028.png b/documentation/html/stories/GrandmasPhoto/images/00028.png new file mode 100644 index 0000000..74fcea6 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00028.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00029.html b/documentation/html/stories/GrandmasPhoto/images/00029.html new file mode 100644 index 0000000..668c8b3 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00029.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00029.png b/documentation/html/stories/GrandmasPhoto/images/00029.png new file mode 100644 index 0000000..c34154c Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00029.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00030.html b/documentation/html/stories/GrandmasPhoto/images/00030.html new file mode 100644 index 0000000..f377d87 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00030.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00030.png b/documentation/html/stories/GrandmasPhoto/images/00030.png new file mode 100644 index 0000000..604cf86 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00030.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00031.html b/documentation/html/stories/GrandmasPhoto/images/00031.html new file mode 100644 index 0000000..3df9650 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00031.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00031.png b/documentation/html/stories/GrandmasPhoto/images/00031.png new file mode 100644 index 0000000..e14836e Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00031.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00032.html b/documentation/html/stories/GrandmasPhoto/images/00032.html new file mode 100644 index 0000000..87a8f38 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00032.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00032.png b/documentation/html/stories/GrandmasPhoto/images/00032.png new file mode 100644 index 0000000..cc859af Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00032.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00033.html b/documentation/html/stories/GrandmasPhoto/images/00033.html new file mode 100644 index 0000000..4a723d9 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00033.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00033.png b/documentation/html/stories/GrandmasPhoto/images/00033.png new file mode 100644 index 0000000..91516e5 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00033.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00034.html b/documentation/html/stories/GrandmasPhoto/images/00034.html new file mode 100644 index 0000000..676a294 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00034.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00034.png b/documentation/html/stories/GrandmasPhoto/images/00034.png new file mode 100644 index 0000000..2174e01 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00034.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00035.html b/documentation/html/stories/GrandmasPhoto/images/00035.html new file mode 100644 index 0000000..07974ea --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00035.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00035.png b/documentation/html/stories/GrandmasPhoto/images/00035.png new file mode 100644 index 0000000..2a54d4b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00035.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00036.html b/documentation/html/stories/GrandmasPhoto/images/00036.html new file mode 100644 index 0000000..e1089a9 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00036.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00036.png b/documentation/html/stories/GrandmasPhoto/images/00036.png new file mode 100644 index 0000000..494321a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00036.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00037.html b/documentation/html/stories/GrandmasPhoto/images/00037.html new file mode 100644 index 0000000..f094e62 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00037.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00037.png b/documentation/html/stories/GrandmasPhoto/images/00037.png new file mode 100644 index 0000000..dc7407c Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00037.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00038.html b/documentation/html/stories/GrandmasPhoto/images/00038.html new file mode 100644 index 0000000..4c9818b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00038.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00038.png b/documentation/html/stories/GrandmasPhoto/images/00038.png new file mode 100644 index 0000000..51d3840 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00038.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00039.html b/documentation/html/stories/GrandmasPhoto/images/00039.html new file mode 100644 index 0000000..a5909de --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00039.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00039.png b/documentation/html/stories/GrandmasPhoto/images/00039.png new file mode 100644 index 0000000..b4751f9 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00039.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00040.html b/documentation/html/stories/GrandmasPhoto/images/00040.html new file mode 100644 index 0000000..e193a8b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00040.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00040.png b/documentation/html/stories/GrandmasPhoto/images/00040.png new file mode 100644 index 0000000..2474369 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00040.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00041.html b/documentation/html/stories/GrandmasPhoto/images/00041.html new file mode 100644 index 0000000..92477c6 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00041.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00041.png b/documentation/html/stories/GrandmasPhoto/images/00041.png new file mode 100644 index 0000000..7467428 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00041.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00042.html b/documentation/html/stories/GrandmasPhoto/images/00042.html new file mode 100644 index 0000000..92c3380 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00042.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00042.png b/documentation/html/stories/GrandmasPhoto/images/00042.png new file mode 100644 index 0000000..091fbad Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00042.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00043.html b/documentation/html/stories/GrandmasPhoto/images/00043.html new file mode 100644 index 0000000..9c6c21f --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00043.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00043.png b/documentation/html/stories/GrandmasPhoto/images/00043.png new file mode 100644 index 0000000..14d75d1 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00043.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00044.html b/documentation/html/stories/GrandmasPhoto/images/00044.html new file mode 100644 index 0000000..cd82263 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00044.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00044.png b/documentation/html/stories/GrandmasPhoto/images/00044.png new file mode 100644 index 0000000..663e127 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00044.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00045.html b/documentation/html/stories/GrandmasPhoto/images/00045.html new file mode 100644 index 0000000..5ff3fc1 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00045.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00045.png b/documentation/html/stories/GrandmasPhoto/images/00045.png new file mode 100644 index 0000000..98ff455 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00045.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00046.html b/documentation/html/stories/GrandmasPhoto/images/00046.html new file mode 100644 index 0000000..5222065 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00046.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00046.png b/documentation/html/stories/GrandmasPhoto/images/00046.png new file mode 100644 index 0000000..17f6d0b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00046.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00047.html b/documentation/html/stories/GrandmasPhoto/images/00047.html new file mode 100644 index 0000000..36754e7 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00047.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00047.png b/documentation/html/stories/GrandmasPhoto/images/00047.png new file mode 100644 index 0000000..a0a1d50 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00047.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00048.html b/documentation/html/stories/GrandmasPhoto/images/00048.html new file mode 100644 index 0000000..fcea1cb --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00048.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00048.png b/documentation/html/stories/GrandmasPhoto/images/00048.png new file mode 100644 index 0000000..d2699ac Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00048.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00049.html b/documentation/html/stories/GrandmasPhoto/images/00049.html new file mode 100644 index 0000000..69512a0 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00049.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00049.png b/documentation/html/stories/GrandmasPhoto/images/00049.png new file mode 100644 index 0000000..94e723a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00049.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00050.html b/documentation/html/stories/GrandmasPhoto/images/00050.html new file mode 100644 index 0000000..2f19cd7 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00050.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00050.png b/documentation/html/stories/GrandmasPhoto/images/00050.png new file mode 100644 index 0000000..adbabef Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00050.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00051.html b/documentation/html/stories/GrandmasPhoto/images/00051.html new file mode 100644 index 0000000..94e9dc6 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00051.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00051.png b/documentation/html/stories/GrandmasPhoto/images/00051.png new file mode 100644 index 0000000..dbcb003 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00051.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00052.html b/documentation/html/stories/GrandmasPhoto/images/00052.html new file mode 100644 index 0000000..84ecfcd --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00052.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00052.png b/documentation/html/stories/GrandmasPhoto/images/00052.png new file mode 100644 index 0000000..a157b4b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00052.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00053.html b/documentation/html/stories/GrandmasPhoto/images/00053.html new file mode 100644 index 0000000..ef24385 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00053.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00053.png b/documentation/html/stories/GrandmasPhoto/images/00053.png new file mode 100644 index 0000000..c76d06e Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00053.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00054.html b/documentation/html/stories/GrandmasPhoto/images/00054.html new file mode 100644 index 0000000..6f07380 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00054.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00054.png b/documentation/html/stories/GrandmasPhoto/images/00054.png new file mode 100644 index 0000000..f641461 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00054.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00055.html b/documentation/html/stories/GrandmasPhoto/images/00055.html new file mode 100644 index 0000000..d58cc49 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00055.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00055.png b/documentation/html/stories/GrandmasPhoto/images/00055.png new file mode 100644 index 0000000..0b4bd19 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00055.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00056.html b/documentation/html/stories/GrandmasPhoto/images/00056.html new file mode 100644 index 0000000..92ce6cd --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00056.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00056.png b/documentation/html/stories/GrandmasPhoto/images/00056.png new file mode 100644 index 0000000..b17fcc6 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00056.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00057.html b/documentation/html/stories/GrandmasPhoto/images/00057.html new file mode 100644 index 0000000..470792c --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00057.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00057.png b/documentation/html/stories/GrandmasPhoto/images/00057.png new file mode 100644 index 0000000..4403fcc Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00057.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00058.html b/documentation/html/stories/GrandmasPhoto/images/00058.html new file mode 100644 index 0000000..83ce1d7 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00058.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00058.png b/documentation/html/stories/GrandmasPhoto/images/00058.png new file mode 100644 index 0000000..d2ef398 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00058.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00059.html b/documentation/html/stories/GrandmasPhoto/images/00059.html new file mode 100644 index 0000000..e40ee1e --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00059.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00059.png b/documentation/html/stories/GrandmasPhoto/images/00059.png new file mode 100644 index 0000000..e2a00ad Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00059.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00060.html b/documentation/html/stories/GrandmasPhoto/images/00060.html new file mode 100644 index 0000000..848deef --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00060.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00060.png b/documentation/html/stories/GrandmasPhoto/images/00060.png new file mode 100644 index 0000000..fbe4eab Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00060.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00061.html b/documentation/html/stories/GrandmasPhoto/images/00061.html new file mode 100644 index 0000000..2d0370a --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00061.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00061.png b/documentation/html/stories/GrandmasPhoto/images/00061.png new file mode 100644 index 0000000..cfc87aa Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00061.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00062.html b/documentation/html/stories/GrandmasPhoto/images/00062.html new file mode 100644 index 0000000..8920c23 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00062.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00062.png b/documentation/html/stories/GrandmasPhoto/images/00062.png new file mode 100644 index 0000000..7ab9aa7 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00062.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00063.html b/documentation/html/stories/GrandmasPhoto/images/00063.html new file mode 100644 index 0000000..8cfeb6c --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00063.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00063.png b/documentation/html/stories/GrandmasPhoto/images/00063.png new file mode 100644 index 0000000..4029410 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00063.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00064.html b/documentation/html/stories/GrandmasPhoto/images/00064.html new file mode 100644 index 0000000..a63153a --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00064.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00064.png b/documentation/html/stories/GrandmasPhoto/images/00064.png new file mode 100644 index 0000000..bc99b6a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00064.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00065.html b/documentation/html/stories/GrandmasPhoto/images/00065.html new file mode 100644 index 0000000..6e1318a --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00065.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00065.png b/documentation/html/stories/GrandmasPhoto/images/00065.png new file mode 100644 index 0000000..0d06c77 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00065.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00066.html b/documentation/html/stories/GrandmasPhoto/images/00066.html new file mode 100644 index 0000000..af474e4 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00066.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00066.png b/documentation/html/stories/GrandmasPhoto/images/00066.png new file mode 100644 index 0000000..2e9aab4 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00066.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00067.html b/documentation/html/stories/GrandmasPhoto/images/00067.html new file mode 100644 index 0000000..83f3253 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00067.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00067.png b/documentation/html/stories/GrandmasPhoto/images/00067.png new file mode 100644 index 0000000..a8ff293 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00067.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00068.html b/documentation/html/stories/GrandmasPhoto/images/00068.html new file mode 100644 index 0000000..a7b3cbe --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00068.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00068.png b/documentation/html/stories/GrandmasPhoto/images/00068.png new file mode 100644 index 0000000..08fdb5c Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00068.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00069.html b/documentation/html/stories/GrandmasPhoto/images/00069.html new file mode 100644 index 0000000..bea7759 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00069.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00069.png b/documentation/html/stories/GrandmasPhoto/images/00069.png new file mode 100644 index 0000000..c3c667a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00069.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00070.html b/documentation/html/stories/GrandmasPhoto/images/00070.html new file mode 100644 index 0000000..38e2410 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00070.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00070.png b/documentation/html/stories/GrandmasPhoto/images/00070.png new file mode 100644 index 0000000..315292f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00070.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00071.html b/documentation/html/stories/GrandmasPhoto/images/00071.html new file mode 100644 index 0000000..694f6a0 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00071.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00071.png b/documentation/html/stories/GrandmasPhoto/images/00071.png new file mode 100644 index 0000000..c536d8f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00071.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00072.html b/documentation/html/stories/GrandmasPhoto/images/00072.html new file mode 100644 index 0000000..e795714 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00072.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00072.png b/documentation/html/stories/GrandmasPhoto/images/00072.png new file mode 100644 index 0000000..e4eb141 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00072.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00073.html b/documentation/html/stories/GrandmasPhoto/images/00073.html new file mode 100644 index 0000000..11e3427 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00073.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00073.png b/documentation/html/stories/GrandmasPhoto/images/00073.png new file mode 100644 index 0000000..225ea3f Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00073.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00074.html b/documentation/html/stories/GrandmasPhoto/images/00074.html new file mode 100644 index 0000000..1ed372a --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00074.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00074.png b/documentation/html/stories/GrandmasPhoto/images/00074.png new file mode 100644 index 0000000..fd7d57b Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00074.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00075.html b/documentation/html/stories/GrandmasPhoto/images/00075.html new file mode 100644 index 0000000..038477d --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00075.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00075.png b/documentation/html/stories/GrandmasPhoto/images/00075.png new file mode 100644 index 0000000..c3c667a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00075.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00076.html b/documentation/html/stories/GrandmasPhoto/images/00076.html new file mode 100644 index 0000000..ff0f5df --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00076.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00076.png b/documentation/html/stories/GrandmasPhoto/images/00076.png new file mode 100644 index 0000000..ab730fa Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00076.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00077.html b/documentation/html/stories/GrandmasPhoto/images/00077.html new file mode 100644 index 0000000..a98b83c --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00077.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00077.png b/documentation/html/stories/GrandmasPhoto/images/00077.png new file mode 100644 index 0000000..3f2802a Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00077.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00078.html b/documentation/html/stories/GrandmasPhoto/images/00078.html new file mode 100644 index 0000000..e836a5d --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00078.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00078.png b/documentation/html/stories/GrandmasPhoto/images/00078.png new file mode 100644 index 0000000..63c5398 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00078.png differ diff --git a/documentation/html/stories/GrandmasPhoto/images/00079.html b/documentation/html/stories/GrandmasPhoto/images/00079.html new file mode 100644 index 0000000..b6e9d99 --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/images/00079.html @@ -0,0 +1,85 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/images/00079.png b/documentation/html/stories/GrandmasPhoto/images/00079.png new file mode 100644 index 0000000..10bafe6 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/images/00079.png differ diff --git a/documentation/html/stories/GrandmasPhoto/index.php b/documentation/html/stories/GrandmasPhoto/index.php new file mode 100644 index 0000000..dd0c52b --- /dev/null +++ b/documentation/html/stories/GrandmasPhoto/index.php @@ -0,0 +1,249 @@ +"; + + + +// clicking on image goes to next + +if( $next != -1 ) { + + echo ""; + + } + +echo ""; + +if( $next != -1 ) { + + echo ""; + + } + + + +echo ""; + + + + + +echo ""; + + + +echo ""; + +if( $prev != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + +if( $next != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + + + + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + + + +if( $next != -1 ) { + + echo ""; + + } + + + + + + + +include( "footer.php" ); + + + +?> \ No newline at end of file diff --git a/documentation/html/stories/GrandmasPhoto/next.png b/documentation/html/stories/GrandmasPhoto/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/next.png differ diff --git a/documentation/html/stories/GrandmasPhoto/prev.png b/documentation/html/stories/GrandmasPhoto/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/documentation/html/stories/GrandmasPhoto/prev.png differ diff --git a/documentation/html/stories/Pareidolia.png b/documentation/html/stories/Pareidolia.png new file mode 100644 index 0000000..bb4f3c9 Binary files /dev/null and b/documentation/html/stories/Pareidolia.png differ diff --git a/documentation/html/stories/Pareidolia/bakIndex.html b/documentation/html/stories/Pareidolia/bakIndex.html new file mode 100644 index 0000000..86ff9eb --- /dev/null +++ b/documentation/html/stories/Pareidolia/bakIndex.html @@ -0,0 +1,81 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/footer.php b/documentation/html/stories/Pareidolia/footer.php new file mode 100644 index 0000000..1c02674 --- /dev/null +++ b/documentation/html/stories/Pareidolia/footer.php @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/frameList.php b/documentation/html/stories/Pareidolia/frameList.php new file mode 100644 index 0000000..72a9520 --- /dev/null +++ b/documentation/html/stories/Pareidolia/frameList.php @@ -0,0 +1 @@ + diff --git a/documentation/html/stories/Pareidolia/header.php b/documentation/html/stories/Pareidolia/header.php new file mode 100644 index 0000000..9ebebd9 --- /dev/null +++ b/documentation/html/stories/Pareidolia/header.php @@ -0,0 +1,13 @@ + + + +Sleep Is Death Flip Book + + + + + +[home] [order] [start] + +
    \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00001.html b/documentation/html/stories/Pareidolia/images/00001.html new file mode 100644 index 0000000..6d0ada6 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00001.html @@ -0,0 +1,87 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + +
    + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00001.png b/documentation/html/stories/Pareidolia/images/00001.png new file mode 100644 index 0000000..ea681f3 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00001.png differ diff --git a/documentation/html/stories/Pareidolia/images/00002.html b/documentation/html/stories/Pareidolia/images/00002.html new file mode 100644 index 0000000..143bd50 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00002.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00002.png b/documentation/html/stories/Pareidolia/images/00002.png new file mode 100644 index 0000000..d1da098 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00002.png differ diff --git a/documentation/html/stories/Pareidolia/images/00003.html b/documentation/html/stories/Pareidolia/images/00003.html new file mode 100644 index 0000000..a1ecc19 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00003.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00003.png b/documentation/html/stories/Pareidolia/images/00003.png new file mode 100644 index 0000000..66b0c82 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00003.png differ diff --git a/documentation/html/stories/Pareidolia/images/00004.html b/documentation/html/stories/Pareidolia/images/00004.html new file mode 100644 index 0000000..2b21e71 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00004.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00004.png b/documentation/html/stories/Pareidolia/images/00004.png new file mode 100644 index 0000000..b1a6788 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00004.png differ diff --git a/documentation/html/stories/Pareidolia/images/00005.html b/documentation/html/stories/Pareidolia/images/00005.html new file mode 100644 index 0000000..50fb37d --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00005.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00005.png b/documentation/html/stories/Pareidolia/images/00005.png new file mode 100644 index 0000000..31e085b Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00005.png differ diff --git a/documentation/html/stories/Pareidolia/images/00006.html b/documentation/html/stories/Pareidolia/images/00006.html new file mode 100644 index 0000000..e938ae9 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00006.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00006.png b/documentation/html/stories/Pareidolia/images/00006.png new file mode 100644 index 0000000..6c3dd14 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00006.png differ diff --git a/documentation/html/stories/Pareidolia/images/00007.html b/documentation/html/stories/Pareidolia/images/00007.html new file mode 100644 index 0000000..972409b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00007.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00007.png b/documentation/html/stories/Pareidolia/images/00007.png new file mode 100644 index 0000000..50f83b0 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00007.png differ diff --git a/documentation/html/stories/Pareidolia/images/00008.html b/documentation/html/stories/Pareidolia/images/00008.html new file mode 100644 index 0000000..a8b8d20 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00008.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00008.png b/documentation/html/stories/Pareidolia/images/00008.png new file mode 100644 index 0000000..bb4f3c9 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00008.png differ diff --git a/documentation/html/stories/Pareidolia/images/00009.html b/documentation/html/stories/Pareidolia/images/00009.html new file mode 100644 index 0000000..ed49def --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00009.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00009.png b/documentation/html/stories/Pareidolia/images/00009.png new file mode 100644 index 0000000..3845c76 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00009.png differ diff --git a/documentation/html/stories/Pareidolia/images/00010.html b/documentation/html/stories/Pareidolia/images/00010.html new file mode 100644 index 0000000..0a2179b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00010.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00010.png b/documentation/html/stories/Pareidolia/images/00010.png new file mode 100644 index 0000000..bbf924e Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00010.png differ diff --git a/documentation/html/stories/Pareidolia/images/00011.html b/documentation/html/stories/Pareidolia/images/00011.html new file mode 100644 index 0000000..a43d31f --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00011.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00011.png b/documentation/html/stories/Pareidolia/images/00011.png new file mode 100644 index 0000000..31e0be6 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00011.png differ diff --git a/documentation/html/stories/Pareidolia/images/00012.html b/documentation/html/stories/Pareidolia/images/00012.html new file mode 100644 index 0000000..34d4e7c --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00012.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00012.png b/documentation/html/stories/Pareidolia/images/00012.png new file mode 100644 index 0000000..bb4f3c9 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00012.png differ diff --git a/documentation/html/stories/Pareidolia/images/00013.html b/documentation/html/stories/Pareidolia/images/00013.html new file mode 100644 index 0000000..fc095d9 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00013.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00013.png b/documentation/html/stories/Pareidolia/images/00013.png new file mode 100644 index 0000000..fb6bceb Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00013.png differ diff --git a/documentation/html/stories/Pareidolia/images/00014.html b/documentation/html/stories/Pareidolia/images/00014.html new file mode 100644 index 0000000..bbfd475 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00014.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00014.png b/documentation/html/stories/Pareidolia/images/00014.png new file mode 100644 index 0000000..214dc73 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00014.png differ diff --git a/documentation/html/stories/Pareidolia/images/00015.html b/documentation/html/stories/Pareidolia/images/00015.html new file mode 100644 index 0000000..3aecee0 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00015.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00015.png b/documentation/html/stories/Pareidolia/images/00015.png new file mode 100644 index 0000000..3ef3af6 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00015.png differ diff --git a/documentation/html/stories/Pareidolia/images/00016.html b/documentation/html/stories/Pareidolia/images/00016.html new file mode 100644 index 0000000..3f9368f --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00016.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00016.png b/documentation/html/stories/Pareidolia/images/00016.png new file mode 100644 index 0000000..bb4f3c9 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00016.png differ diff --git a/documentation/html/stories/Pareidolia/images/00017.html b/documentation/html/stories/Pareidolia/images/00017.html new file mode 100644 index 0000000..db115c5 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00017.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00017.png b/documentation/html/stories/Pareidolia/images/00017.png new file mode 100644 index 0000000..eace6b1 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00017.png differ diff --git a/documentation/html/stories/Pareidolia/images/00018.html b/documentation/html/stories/Pareidolia/images/00018.html new file mode 100644 index 0000000..d263f96 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00018.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00018.png b/documentation/html/stories/Pareidolia/images/00018.png new file mode 100644 index 0000000..5772e33 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00018.png differ diff --git a/documentation/html/stories/Pareidolia/images/00019.html b/documentation/html/stories/Pareidolia/images/00019.html new file mode 100644 index 0000000..d0ee17b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00019.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00019.png b/documentation/html/stories/Pareidolia/images/00019.png new file mode 100644 index 0000000..c94ce3a Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00019.png differ diff --git a/documentation/html/stories/Pareidolia/images/00020.html b/documentation/html/stories/Pareidolia/images/00020.html new file mode 100644 index 0000000..dd84502 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00020.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00020.png b/documentation/html/stories/Pareidolia/images/00020.png new file mode 100644 index 0000000..4c66a35 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00020.png differ diff --git a/documentation/html/stories/Pareidolia/images/00021.html b/documentation/html/stories/Pareidolia/images/00021.html new file mode 100644 index 0000000..bcc5c25 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00021.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00021.png b/documentation/html/stories/Pareidolia/images/00021.png new file mode 100644 index 0000000..4c66a35 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00021.png differ diff --git a/documentation/html/stories/Pareidolia/images/00022.html b/documentation/html/stories/Pareidolia/images/00022.html new file mode 100644 index 0000000..f4aea77 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00022.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00022.png b/documentation/html/stories/Pareidolia/images/00022.png new file mode 100644 index 0000000..5cf2ce0 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00022.png differ diff --git a/documentation/html/stories/Pareidolia/images/00023.html b/documentation/html/stories/Pareidolia/images/00023.html new file mode 100644 index 0000000..eff5548 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00023.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00023.png b/documentation/html/stories/Pareidolia/images/00023.png new file mode 100644 index 0000000..0393605 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00023.png differ diff --git a/documentation/html/stories/Pareidolia/images/00024.html b/documentation/html/stories/Pareidolia/images/00024.html new file mode 100644 index 0000000..910351b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00024.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00024.png b/documentation/html/stories/Pareidolia/images/00024.png new file mode 100644 index 0000000..909bf8c Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00024.png differ diff --git a/documentation/html/stories/Pareidolia/images/00025.html b/documentation/html/stories/Pareidolia/images/00025.html new file mode 100644 index 0000000..a384d8f --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00025.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00025.png b/documentation/html/stories/Pareidolia/images/00025.png new file mode 100644 index 0000000..99148d4 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00025.png differ diff --git a/documentation/html/stories/Pareidolia/images/00026.html b/documentation/html/stories/Pareidolia/images/00026.html new file mode 100644 index 0000000..3e2ef4c --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00026.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00026.png b/documentation/html/stories/Pareidolia/images/00026.png new file mode 100644 index 0000000..f29a2d9 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00026.png differ diff --git a/documentation/html/stories/Pareidolia/images/00027.html b/documentation/html/stories/Pareidolia/images/00027.html new file mode 100644 index 0000000..ac8daaf --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00027.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00027.png b/documentation/html/stories/Pareidolia/images/00027.png new file mode 100644 index 0000000..1e129bf Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00027.png differ diff --git a/documentation/html/stories/Pareidolia/images/00028.html b/documentation/html/stories/Pareidolia/images/00028.html new file mode 100644 index 0000000..c4fdfdb --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00028.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00028.png b/documentation/html/stories/Pareidolia/images/00028.png new file mode 100644 index 0000000..d9d8c41 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00028.png differ diff --git a/documentation/html/stories/Pareidolia/images/00029.html b/documentation/html/stories/Pareidolia/images/00029.html new file mode 100644 index 0000000..668c8b3 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00029.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00029.png b/documentation/html/stories/Pareidolia/images/00029.png new file mode 100644 index 0000000..3162d40 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00029.png differ diff --git a/documentation/html/stories/Pareidolia/images/00030.html b/documentation/html/stories/Pareidolia/images/00030.html new file mode 100644 index 0000000..f377d87 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00030.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00030.png b/documentation/html/stories/Pareidolia/images/00030.png new file mode 100644 index 0000000..aef92d5 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00030.png differ diff --git a/documentation/html/stories/Pareidolia/images/00031.html b/documentation/html/stories/Pareidolia/images/00031.html new file mode 100644 index 0000000..3df9650 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00031.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00031.png b/documentation/html/stories/Pareidolia/images/00031.png new file mode 100644 index 0000000..6dee581 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00031.png differ diff --git a/documentation/html/stories/Pareidolia/images/00032.html b/documentation/html/stories/Pareidolia/images/00032.html new file mode 100644 index 0000000..87a8f38 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00032.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00032.png b/documentation/html/stories/Pareidolia/images/00032.png new file mode 100644 index 0000000..4c66a35 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00032.png differ diff --git a/documentation/html/stories/Pareidolia/images/00033.html b/documentation/html/stories/Pareidolia/images/00033.html new file mode 100644 index 0000000..4a723d9 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00033.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00033.png b/documentation/html/stories/Pareidolia/images/00033.png new file mode 100644 index 0000000..fe5591c Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00033.png differ diff --git a/documentation/html/stories/Pareidolia/images/00034.html b/documentation/html/stories/Pareidolia/images/00034.html new file mode 100644 index 0000000..676a294 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00034.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00034.png b/documentation/html/stories/Pareidolia/images/00034.png new file mode 100644 index 0000000..fb69b2b Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00034.png differ diff --git a/documentation/html/stories/Pareidolia/images/00035.html b/documentation/html/stories/Pareidolia/images/00035.html new file mode 100644 index 0000000..07974ea --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00035.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00035.png b/documentation/html/stories/Pareidolia/images/00035.png new file mode 100644 index 0000000..05ec958 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00035.png differ diff --git a/documentation/html/stories/Pareidolia/images/00036.html b/documentation/html/stories/Pareidolia/images/00036.html new file mode 100644 index 0000000..e1089a9 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00036.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00036.png b/documentation/html/stories/Pareidolia/images/00036.png new file mode 100644 index 0000000..3eddbc8 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00036.png differ diff --git a/documentation/html/stories/Pareidolia/images/00037.html b/documentation/html/stories/Pareidolia/images/00037.html new file mode 100644 index 0000000..f094e62 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00037.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00037.png b/documentation/html/stories/Pareidolia/images/00037.png new file mode 100644 index 0000000..6e5a18e Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00037.png differ diff --git a/documentation/html/stories/Pareidolia/images/00038.html b/documentation/html/stories/Pareidolia/images/00038.html new file mode 100644 index 0000000..4c9818b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00038.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00038.png b/documentation/html/stories/Pareidolia/images/00038.png new file mode 100644 index 0000000..f01524d Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00038.png differ diff --git a/documentation/html/stories/Pareidolia/images/00039.html b/documentation/html/stories/Pareidolia/images/00039.html new file mode 100644 index 0000000..a5909de --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00039.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00039.png b/documentation/html/stories/Pareidolia/images/00039.png new file mode 100644 index 0000000..64b5091 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00039.png differ diff --git a/documentation/html/stories/Pareidolia/images/00040.html b/documentation/html/stories/Pareidolia/images/00040.html new file mode 100644 index 0000000..e193a8b --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00040.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00040.png b/documentation/html/stories/Pareidolia/images/00040.png new file mode 100644 index 0000000..c63032f Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00040.png differ diff --git a/documentation/html/stories/Pareidolia/images/00041.html b/documentation/html/stories/Pareidolia/images/00041.html new file mode 100644 index 0000000..92477c6 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00041.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00041.png b/documentation/html/stories/Pareidolia/images/00041.png new file mode 100644 index 0000000..3286e63 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00041.png differ diff --git a/documentation/html/stories/Pareidolia/images/00042.html b/documentation/html/stories/Pareidolia/images/00042.html new file mode 100644 index 0000000..92c3380 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00042.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00042.png b/documentation/html/stories/Pareidolia/images/00042.png new file mode 100644 index 0000000..01da010 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00042.png differ diff --git a/documentation/html/stories/Pareidolia/images/00043.html b/documentation/html/stories/Pareidolia/images/00043.html new file mode 100644 index 0000000..9c6c21f --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00043.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00043.png b/documentation/html/stories/Pareidolia/images/00043.png new file mode 100644 index 0000000..f0ce444 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00043.png differ diff --git a/documentation/html/stories/Pareidolia/images/00044.html b/documentation/html/stories/Pareidolia/images/00044.html new file mode 100644 index 0000000..cd82263 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00044.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00044.png b/documentation/html/stories/Pareidolia/images/00044.png new file mode 100644 index 0000000..41f1a29 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00044.png differ diff --git a/documentation/html/stories/Pareidolia/images/00045.html b/documentation/html/stories/Pareidolia/images/00045.html new file mode 100644 index 0000000..5ff3fc1 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00045.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00045.png b/documentation/html/stories/Pareidolia/images/00045.png new file mode 100644 index 0000000..e8fb2c2 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00045.png differ diff --git a/documentation/html/stories/Pareidolia/images/00046.html b/documentation/html/stories/Pareidolia/images/00046.html new file mode 100644 index 0000000..5222065 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00046.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00046.png b/documentation/html/stories/Pareidolia/images/00046.png new file mode 100644 index 0000000..5755cee Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00046.png differ diff --git a/documentation/html/stories/Pareidolia/images/00047.html b/documentation/html/stories/Pareidolia/images/00047.html new file mode 100644 index 0000000..36754e7 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00047.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00047.png b/documentation/html/stories/Pareidolia/images/00047.png new file mode 100644 index 0000000..72336d1 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00047.png differ diff --git a/documentation/html/stories/Pareidolia/images/00048.html b/documentation/html/stories/Pareidolia/images/00048.html new file mode 100644 index 0000000..fcea1cb --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00048.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00048.png b/documentation/html/stories/Pareidolia/images/00048.png new file mode 100644 index 0000000..299ec9d Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00048.png differ diff --git a/documentation/html/stories/Pareidolia/images/00049.html b/documentation/html/stories/Pareidolia/images/00049.html new file mode 100644 index 0000000..69512a0 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00049.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00049.png b/documentation/html/stories/Pareidolia/images/00049.png new file mode 100644 index 0000000..682191d Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00049.png differ diff --git a/documentation/html/stories/Pareidolia/images/00050.html b/documentation/html/stories/Pareidolia/images/00050.html new file mode 100644 index 0000000..2f19cd7 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00050.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00050.png b/documentation/html/stories/Pareidolia/images/00050.png new file mode 100644 index 0000000..acb5751 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00050.png differ diff --git a/documentation/html/stories/Pareidolia/images/00051.html b/documentation/html/stories/Pareidolia/images/00051.html new file mode 100644 index 0000000..94e9dc6 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00051.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00051.png b/documentation/html/stories/Pareidolia/images/00051.png new file mode 100644 index 0000000..ac509eb Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00051.png differ diff --git a/documentation/html/stories/Pareidolia/images/00052.html b/documentation/html/stories/Pareidolia/images/00052.html new file mode 100644 index 0000000..84ecfcd --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00052.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00052.png b/documentation/html/stories/Pareidolia/images/00052.png new file mode 100644 index 0000000..4fcac7b Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00052.png differ diff --git a/documentation/html/stories/Pareidolia/images/00053.html b/documentation/html/stories/Pareidolia/images/00053.html new file mode 100644 index 0000000..ef24385 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00053.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00053.png b/documentation/html/stories/Pareidolia/images/00053.png new file mode 100644 index 0000000..fb06e51 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00053.png differ diff --git a/documentation/html/stories/Pareidolia/images/00054.html b/documentation/html/stories/Pareidolia/images/00054.html new file mode 100644 index 0000000..6f07380 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00054.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00054.png b/documentation/html/stories/Pareidolia/images/00054.png new file mode 100644 index 0000000..4c66a35 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00054.png differ diff --git a/documentation/html/stories/Pareidolia/images/00055.html b/documentation/html/stories/Pareidolia/images/00055.html new file mode 100644 index 0000000..d58cc49 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00055.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00055.png b/documentation/html/stories/Pareidolia/images/00055.png new file mode 100644 index 0000000..5901ff1 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00055.png differ diff --git a/documentation/html/stories/Pareidolia/images/00056.html b/documentation/html/stories/Pareidolia/images/00056.html new file mode 100644 index 0000000..92ce6cd --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00056.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00056.png b/documentation/html/stories/Pareidolia/images/00056.png new file mode 100644 index 0000000..58466c9 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00056.png differ diff --git a/documentation/html/stories/Pareidolia/images/00057.html b/documentation/html/stories/Pareidolia/images/00057.html new file mode 100644 index 0000000..470792c --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00057.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00057.png b/documentation/html/stories/Pareidolia/images/00057.png new file mode 100644 index 0000000..6325ce8 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00057.png differ diff --git a/documentation/html/stories/Pareidolia/images/00058.html b/documentation/html/stories/Pareidolia/images/00058.html new file mode 100644 index 0000000..83ce1d7 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00058.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00058.png b/documentation/html/stories/Pareidolia/images/00058.png new file mode 100644 index 0000000..d1d161e Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00058.png differ diff --git a/documentation/html/stories/Pareidolia/images/00059.html b/documentation/html/stories/Pareidolia/images/00059.html new file mode 100644 index 0000000..e40ee1e --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00059.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00059.png b/documentation/html/stories/Pareidolia/images/00059.png new file mode 100644 index 0000000..30b85bc Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00059.png differ diff --git a/documentation/html/stories/Pareidolia/images/00060.html b/documentation/html/stories/Pareidolia/images/00060.html new file mode 100644 index 0000000..848deef --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00060.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00060.png b/documentation/html/stories/Pareidolia/images/00060.png new file mode 100644 index 0000000..fa270d0 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00060.png differ diff --git a/documentation/html/stories/Pareidolia/images/00061.html b/documentation/html/stories/Pareidolia/images/00061.html new file mode 100644 index 0000000..2d0370a --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00061.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00061.png b/documentation/html/stories/Pareidolia/images/00061.png new file mode 100644 index 0000000..b6e02a8 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00061.png differ diff --git a/documentation/html/stories/Pareidolia/images/00062.html b/documentation/html/stories/Pareidolia/images/00062.html new file mode 100644 index 0000000..8920c23 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00062.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00062.png b/documentation/html/stories/Pareidolia/images/00062.png new file mode 100644 index 0000000..0e90d79 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00062.png differ diff --git a/documentation/html/stories/Pareidolia/images/00063.html b/documentation/html/stories/Pareidolia/images/00063.html new file mode 100644 index 0000000..8cfeb6c --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00063.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00063.png b/documentation/html/stories/Pareidolia/images/00063.png new file mode 100644 index 0000000..9038386 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00063.png differ diff --git a/documentation/html/stories/Pareidolia/images/00064.html b/documentation/html/stories/Pareidolia/images/00064.html new file mode 100644 index 0000000..a63153a --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00064.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00064.png b/documentation/html/stories/Pareidolia/images/00064.png new file mode 100644 index 0000000..0e90d79 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00064.png differ diff --git a/documentation/html/stories/Pareidolia/images/00065.html b/documentation/html/stories/Pareidolia/images/00065.html new file mode 100644 index 0000000..6e1318a --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00065.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00065.png b/documentation/html/stories/Pareidolia/images/00065.png new file mode 100644 index 0000000..f6aef54 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00065.png differ diff --git a/documentation/html/stories/Pareidolia/images/00066.html b/documentation/html/stories/Pareidolia/images/00066.html new file mode 100644 index 0000000..af474e4 --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00066.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00066.png b/documentation/html/stories/Pareidolia/images/00066.png new file mode 100644 index 0000000..4256d99 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00066.png differ diff --git a/documentation/html/stories/Pareidolia/images/00067.html b/documentation/html/stories/Pareidolia/images/00067.html new file mode 100644 index 0000000..c9ef57f --- /dev/null +++ b/documentation/html/stories/Pareidolia/images/00067.html @@ -0,0 +1,85 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/images/00067.png b/documentation/html/stories/Pareidolia/images/00067.png new file mode 100644 index 0000000..1d967c4 Binary files /dev/null and b/documentation/html/stories/Pareidolia/images/00067.png differ diff --git a/documentation/html/stories/Pareidolia/index.php b/documentation/html/stories/Pareidolia/index.php new file mode 100644 index 0000000..dd0c52b --- /dev/null +++ b/documentation/html/stories/Pareidolia/index.php @@ -0,0 +1,249 @@ +"; + + + +// clicking on image goes to next + +if( $next != -1 ) { + + echo ""; + + } + +echo ""; + +if( $next != -1 ) { + + echo ""; + + } + + + +echo ""; + + + + + +echo ""; + + + +echo ""; + +if( $prev != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + +if( $next != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + + + + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + + + +if( $next != -1 ) { + + echo ""; + + } + + + + + + + +include( "footer.php" ); + + + +?> \ No newline at end of file diff --git a/documentation/html/stories/Pareidolia/next.png b/documentation/html/stories/Pareidolia/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/stories/Pareidolia/next.png differ diff --git a/documentation/html/stories/Pareidolia/prev.png b/documentation/html/stories/Pareidolia/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/documentation/html/stories/Pareidolia/prev.png differ diff --git a/documentation/html/stories/TheOtherOrchard.png b/documentation/html/stories/TheOtherOrchard.png new file mode 100644 index 0000000..d023592 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard.png differ diff --git a/documentation/html/stories/TheOtherOrchard/bakIndex.html b/documentation/html/stories/TheOtherOrchard/bakIndex.html new file mode 100644 index 0000000..86ff9eb --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/bakIndex.html @@ -0,0 +1,81 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/footer.php b/documentation/html/stories/TheOtherOrchard/footer.php new file mode 100644 index 0000000..1c02674 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/footer.php @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/frameList.php b/documentation/html/stories/TheOtherOrchard/frameList.php new file mode 100644 index 0000000..81aa618 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/frameList.php @@ -0,0 +1,15 @@ + diff --git a/documentation/html/stories/TheOtherOrchard/header.php b/documentation/html/stories/TheOtherOrchard/header.php new file mode 100644 index 0000000..9ebebd9 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/header.php @@ -0,0 +1,13 @@ + + + +Sleep Is Death Flip Book + + + + + +[home] [order] [start] + +
    \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00001.html b/documentation/html/stories/TheOtherOrchard/images/00001.html new file mode 100644 index 0000000..6d0ada6 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00001.html @@ -0,0 +1,87 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + +
    + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00001.png b/documentation/html/stories/TheOtherOrchard/images/00001.png new file mode 100644 index 0000000..e33eac3 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00001.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00002.html b/documentation/html/stories/TheOtherOrchard/images/00002.html new file mode 100644 index 0000000..143bd50 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00002.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00002.png b/documentation/html/stories/TheOtherOrchard/images/00002.png new file mode 100644 index 0000000..b4a6995 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00002.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00003.html b/documentation/html/stories/TheOtherOrchard/images/00003.html new file mode 100644 index 0000000..a1ecc19 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00003.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00003.png b/documentation/html/stories/TheOtherOrchard/images/00003.png new file mode 100644 index 0000000..cf083de Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00003.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00004.html b/documentation/html/stories/TheOtherOrchard/images/00004.html new file mode 100644 index 0000000..2b21e71 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00004.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00004.png b/documentation/html/stories/TheOtherOrchard/images/00004.png new file mode 100644 index 0000000..8fd7d58 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00004.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00005.html b/documentation/html/stories/TheOtherOrchard/images/00005.html new file mode 100644 index 0000000..50fb37d --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00005.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00005.png b/documentation/html/stories/TheOtherOrchard/images/00005.png new file mode 100644 index 0000000..1db82da Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00005.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00006.html b/documentation/html/stories/TheOtherOrchard/images/00006.html new file mode 100644 index 0000000..e938ae9 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00006.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00006.png b/documentation/html/stories/TheOtherOrchard/images/00006.png new file mode 100644 index 0000000..99870db Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00006.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00007.html b/documentation/html/stories/TheOtherOrchard/images/00007.html new file mode 100644 index 0000000..972409b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00007.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00007.png b/documentation/html/stories/TheOtherOrchard/images/00007.png new file mode 100644 index 0000000..6de93f5 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00007.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00008.html b/documentation/html/stories/TheOtherOrchard/images/00008.html new file mode 100644 index 0000000..a8b8d20 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00008.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00008.png b/documentation/html/stories/TheOtherOrchard/images/00008.png new file mode 100644 index 0000000..ba57c4f Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00008.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00009.html b/documentation/html/stories/TheOtherOrchard/images/00009.html new file mode 100644 index 0000000..ed49def --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00009.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00009.png b/documentation/html/stories/TheOtherOrchard/images/00009.png new file mode 100644 index 0000000..85580ac Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00009.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00010.html b/documentation/html/stories/TheOtherOrchard/images/00010.html new file mode 100644 index 0000000..0a2179b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00010.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00010.png b/documentation/html/stories/TheOtherOrchard/images/00010.png new file mode 100644 index 0000000..66e1d06 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00010.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00011.html b/documentation/html/stories/TheOtherOrchard/images/00011.html new file mode 100644 index 0000000..a43d31f --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00011.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00011.png b/documentation/html/stories/TheOtherOrchard/images/00011.png new file mode 100644 index 0000000..9c6eb6c Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00011.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00012.html b/documentation/html/stories/TheOtherOrchard/images/00012.html new file mode 100644 index 0000000..34d4e7c --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00012.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00012.png b/documentation/html/stories/TheOtherOrchard/images/00012.png new file mode 100644 index 0000000..f9b667c Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00012.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00013.html b/documentation/html/stories/TheOtherOrchard/images/00013.html new file mode 100644 index 0000000..fc095d9 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00013.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00013.png b/documentation/html/stories/TheOtherOrchard/images/00013.png new file mode 100644 index 0000000..4394de7 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00013.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00014.html b/documentation/html/stories/TheOtherOrchard/images/00014.html new file mode 100644 index 0000000..bbfd475 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00014.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00014.png b/documentation/html/stories/TheOtherOrchard/images/00014.png new file mode 100644 index 0000000..2613ad5 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00014.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00015.html b/documentation/html/stories/TheOtherOrchard/images/00015.html new file mode 100644 index 0000000..3aecee0 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00015.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00015.png b/documentation/html/stories/TheOtherOrchard/images/00015.png new file mode 100644 index 0000000..af75bdd Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00015.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00016.html b/documentation/html/stories/TheOtherOrchard/images/00016.html new file mode 100644 index 0000000..3f9368f --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00016.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00016.png b/documentation/html/stories/TheOtherOrchard/images/00016.png new file mode 100644 index 0000000..e7f580e Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00016.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00017.html b/documentation/html/stories/TheOtherOrchard/images/00017.html new file mode 100644 index 0000000..db115c5 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00017.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00017.png b/documentation/html/stories/TheOtherOrchard/images/00017.png new file mode 100644 index 0000000..3511db0 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00017.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00018.html b/documentation/html/stories/TheOtherOrchard/images/00018.html new file mode 100644 index 0000000..d263f96 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00018.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00018.png b/documentation/html/stories/TheOtherOrchard/images/00018.png new file mode 100644 index 0000000..d023592 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00018.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00019.html b/documentation/html/stories/TheOtherOrchard/images/00019.html new file mode 100644 index 0000000..d0ee17b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00019.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00019.png b/documentation/html/stories/TheOtherOrchard/images/00019.png new file mode 100644 index 0000000..97ba43a Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00019.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00020.html b/documentation/html/stories/TheOtherOrchard/images/00020.html new file mode 100644 index 0000000..dd84502 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00020.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00020.png b/documentation/html/stories/TheOtherOrchard/images/00020.png new file mode 100644 index 0000000..1549815 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00020.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00021.html b/documentation/html/stories/TheOtherOrchard/images/00021.html new file mode 100644 index 0000000..bcc5c25 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00021.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00021.png b/documentation/html/stories/TheOtherOrchard/images/00021.png new file mode 100644 index 0000000..913c623 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00021.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00022.html b/documentation/html/stories/TheOtherOrchard/images/00022.html new file mode 100644 index 0000000..f4aea77 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00022.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00022.png b/documentation/html/stories/TheOtherOrchard/images/00022.png new file mode 100644 index 0000000..fb190e3 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00022.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00023.html b/documentation/html/stories/TheOtherOrchard/images/00023.html new file mode 100644 index 0000000..eff5548 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00023.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00023.png b/documentation/html/stories/TheOtherOrchard/images/00023.png new file mode 100644 index 0000000..781c57b Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00023.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00024.html b/documentation/html/stories/TheOtherOrchard/images/00024.html new file mode 100644 index 0000000..910351b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00024.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00024.png b/documentation/html/stories/TheOtherOrchard/images/00024.png new file mode 100644 index 0000000..ba32f88 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00024.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00025.html b/documentation/html/stories/TheOtherOrchard/images/00025.html new file mode 100644 index 0000000..a384d8f --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00025.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00025.png b/documentation/html/stories/TheOtherOrchard/images/00025.png new file mode 100644 index 0000000..57369b5 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00025.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00026.html b/documentation/html/stories/TheOtherOrchard/images/00026.html new file mode 100644 index 0000000..3e2ef4c --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00026.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00026.png b/documentation/html/stories/TheOtherOrchard/images/00026.png new file mode 100644 index 0000000..f2dfc2f Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00026.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00027.html b/documentation/html/stories/TheOtherOrchard/images/00027.html new file mode 100644 index 0000000..ac8daaf --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00027.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00027.png b/documentation/html/stories/TheOtherOrchard/images/00027.png new file mode 100644 index 0000000..3ab0588 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00027.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00028.html b/documentation/html/stories/TheOtherOrchard/images/00028.html new file mode 100644 index 0000000..c4fdfdb --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00028.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00028.png b/documentation/html/stories/TheOtherOrchard/images/00028.png new file mode 100644 index 0000000..7239f26 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00028.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00029.html b/documentation/html/stories/TheOtherOrchard/images/00029.html new file mode 100644 index 0000000..668c8b3 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00029.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00029.png b/documentation/html/stories/TheOtherOrchard/images/00029.png new file mode 100644 index 0000000..cf3b49c Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00029.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00030.html b/documentation/html/stories/TheOtherOrchard/images/00030.html new file mode 100644 index 0000000..f377d87 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00030.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00030.png b/documentation/html/stories/TheOtherOrchard/images/00030.png new file mode 100644 index 0000000..daf9e22 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00030.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00031.html b/documentation/html/stories/TheOtherOrchard/images/00031.html new file mode 100644 index 0000000..3df9650 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00031.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00031.png b/documentation/html/stories/TheOtherOrchard/images/00031.png new file mode 100644 index 0000000..d01a26e Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00031.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00032.html b/documentation/html/stories/TheOtherOrchard/images/00032.html new file mode 100644 index 0000000..87a8f38 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00032.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00032.png b/documentation/html/stories/TheOtherOrchard/images/00032.png new file mode 100644 index 0000000..4035406 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00032.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00033.html b/documentation/html/stories/TheOtherOrchard/images/00033.html new file mode 100644 index 0000000..4a723d9 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00033.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00033.png b/documentation/html/stories/TheOtherOrchard/images/00033.png new file mode 100644 index 0000000..fbdbb8e Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00033.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00034.html b/documentation/html/stories/TheOtherOrchard/images/00034.html new file mode 100644 index 0000000..676a294 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00034.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00034.png b/documentation/html/stories/TheOtherOrchard/images/00034.png new file mode 100644 index 0000000..a7ead61 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00034.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00035.html b/documentation/html/stories/TheOtherOrchard/images/00035.html new file mode 100644 index 0000000..07974ea --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00035.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00035.png b/documentation/html/stories/TheOtherOrchard/images/00035.png new file mode 100644 index 0000000..12bb524 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00035.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00036.html b/documentation/html/stories/TheOtherOrchard/images/00036.html new file mode 100644 index 0000000..e1089a9 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00036.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00036.png b/documentation/html/stories/TheOtherOrchard/images/00036.png new file mode 100644 index 0000000..4500df4 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00036.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00037.html b/documentation/html/stories/TheOtherOrchard/images/00037.html new file mode 100644 index 0000000..f094e62 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00037.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00037.png b/documentation/html/stories/TheOtherOrchard/images/00037.png new file mode 100644 index 0000000..5849250 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00037.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00038.html b/documentation/html/stories/TheOtherOrchard/images/00038.html new file mode 100644 index 0000000..4c9818b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00038.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00038.png b/documentation/html/stories/TheOtherOrchard/images/00038.png new file mode 100644 index 0000000..ccd3265 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00038.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00039.html b/documentation/html/stories/TheOtherOrchard/images/00039.html new file mode 100644 index 0000000..a5909de --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00039.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00039.png b/documentation/html/stories/TheOtherOrchard/images/00039.png new file mode 100644 index 0000000..b6fedb2 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00039.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00040.html b/documentation/html/stories/TheOtherOrchard/images/00040.html new file mode 100644 index 0000000..e193a8b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00040.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00040.png b/documentation/html/stories/TheOtherOrchard/images/00040.png new file mode 100644 index 0000000..9865dfe Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00040.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00041.html b/documentation/html/stories/TheOtherOrchard/images/00041.html new file mode 100644 index 0000000..92477c6 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00041.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00041.png b/documentation/html/stories/TheOtherOrchard/images/00041.png new file mode 100644 index 0000000..8f8bc84 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00041.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00042.html b/documentation/html/stories/TheOtherOrchard/images/00042.html new file mode 100644 index 0000000..92c3380 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00042.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00042.png b/documentation/html/stories/TheOtherOrchard/images/00042.png new file mode 100644 index 0000000..8f8bc84 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00042.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00043.html b/documentation/html/stories/TheOtherOrchard/images/00043.html new file mode 100644 index 0000000..9c6c21f --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00043.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00043.png b/documentation/html/stories/TheOtherOrchard/images/00043.png new file mode 100644 index 0000000..564a1a3 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00043.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00044.html b/documentation/html/stories/TheOtherOrchard/images/00044.html new file mode 100644 index 0000000..cd82263 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00044.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00044.png b/documentation/html/stories/TheOtherOrchard/images/00044.png new file mode 100644 index 0000000..8b9d115 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00044.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00045.html b/documentation/html/stories/TheOtherOrchard/images/00045.html new file mode 100644 index 0000000..5ff3fc1 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00045.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00045.png b/documentation/html/stories/TheOtherOrchard/images/00045.png new file mode 100644 index 0000000..0cf7442 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00045.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00046.html b/documentation/html/stories/TheOtherOrchard/images/00046.html new file mode 100644 index 0000000..5222065 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00046.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00046.png b/documentation/html/stories/TheOtherOrchard/images/00046.png new file mode 100644 index 0000000..3756fa7 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00046.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00047.html b/documentation/html/stories/TheOtherOrchard/images/00047.html new file mode 100644 index 0000000..36754e7 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00047.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00047.png b/documentation/html/stories/TheOtherOrchard/images/00047.png new file mode 100644 index 0000000..ea132ac Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00047.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00048.html b/documentation/html/stories/TheOtherOrchard/images/00048.html new file mode 100644 index 0000000..fcea1cb --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00048.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00048.png b/documentation/html/stories/TheOtherOrchard/images/00048.png new file mode 100644 index 0000000..50d93e7 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00048.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00049.html b/documentation/html/stories/TheOtherOrchard/images/00049.html new file mode 100644 index 0000000..69512a0 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00049.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00049.png b/documentation/html/stories/TheOtherOrchard/images/00049.png new file mode 100644 index 0000000..a98e0d2 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00049.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00050.html b/documentation/html/stories/TheOtherOrchard/images/00050.html new file mode 100644 index 0000000..2f19cd7 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00050.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00050.png b/documentation/html/stories/TheOtherOrchard/images/00050.png new file mode 100644 index 0000000..1b906a9 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00050.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00051.html b/documentation/html/stories/TheOtherOrchard/images/00051.html new file mode 100644 index 0000000..94e9dc6 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00051.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00051.png b/documentation/html/stories/TheOtherOrchard/images/00051.png new file mode 100644 index 0000000..7aa8784 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00051.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00052.html b/documentation/html/stories/TheOtherOrchard/images/00052.html new file mode 100644 index 0000000..84ecfcd --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00052.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00052.png b/documentation/html/stories/TheOtherOrchard/images/00052.png new file mode 100644 index 0000000..c51cca4 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00052.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00053.html b/documentation/html/stories/TheOtherOrchard/images/00053.html new file mode 100644 index 0000000..ef24385 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00053.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00053.png b/documentation/html/stories/TheOtherOrchard/images/00053.png new file mode 100644 index 0000000..1a5797d Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00053.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00054.html b/documentation/html/stories/TheOtherOrchard/images/00054.html new file mode 100644 index 0000000..6f07380 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00054.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00054.png b/documentation/html/stories/TheOtherOrchard/images/00054.png new file mode 100644 index 0000000..11a1ff9 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00054.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00055.html b/documentation/html/stories/TheOtherOrchard/images/00055.html new file mode 100644 index 0000000..d58cc49 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00055.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00055.png b/documentation/html/stories/TheOtherOrchard/images/00055.png new file mode 100644 index 0000000..e45aa9c Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00055.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00056.html b/documentation/html/stories/TheOtherOrchard/images/00056.html new file mode 100644 index 0000000..92ce6cd --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00056.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00056.png b/documentation/html/stories/TheOtherOrchard/images/00056.png new file mode 100644 index 0000000..ba42b53 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00056.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00057.html b/documentation/html/stories/TheOtherOrchard/images/00057.html new file mode 100644 index 0000000..470792c --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00057.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00057.png b/documentation/html/stories/TheOtherOrchard/images/00057.png new file mode 100644 index 0000000..7be42e1 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00057.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00058.html b/documentation/html/stories/TheOtherOrchard/images/00058.html new file mode 100644 index 0000000..83ce1d7 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00058.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00058.png b/documentation/html/stories/TheOtherOrchard/images/00058.png new file mode 100644 index 0000000..638286b Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00058.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00059.html b/documentation/html/stories/TheOtherOrchard/images/00059.html new file mode 100644 index 0000000..e40ee1e --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00059.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00059.png b/documentation/html/stories/TheOtherOrchard/images/00059.png new file mode 100644 index 0000000..8555655 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00059.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00060.html b/documentation/html/stories/TheOtherOrchard/images/00060.html new file mode 100644 index 0000000..848deef --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00060.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00060.png b/documentation/html/stories/TheOtherOrchard/images/00060.png new file mode 100644 index 0000000..3984cd0 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00060.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00061.html b/documentation/html/stories/TheOtherOrchard/images/00061.html new file mode 100644 index 0000000..2d0370a --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00061.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00061.png b/documentation/html/stories/TheOtherOrchard/images/00061.png new file mode 100644 index 0000000..07ea8cd Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00061.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00062.html b/documentation/html/stories/TheOtherOrchard/images/00062.html new file mode 100644 index 0000000..8920c23 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00062.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00062.png b/documentation/html/stories/TheOtherOrchard/images/00062.png new file mode 100644 index 0000000..317705c Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00062.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00063.html b/documentation/html/stories/TheOtherOrchard/images/00063.html new file mode 100644 index 0000000..8cfeb6c --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00063.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00063.png b/documentation/html/stories/TheOtherOrchard/images/00063.png new file mode 100644 index 0000000..dcd730a Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00063.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00064.html b/documentation/html/stories/TheOtherOrchard/images/00064.html new file mode 100644 index 0000000..a63153a --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00064.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00064.png b/documentation/html/stories/TheOtherOrchard/images/00064.png new file mode 100644 index 0000000..42c0ae5 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00064.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00065.html b/documentation/html/stories/TheOtherOrchard/images/00065.html new file mode 100644 index 0000000..6e1318a --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00065.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00065.png b/documentation/html/stories/TheOtherOrchard/images/00065.png new file mode 100644 index 0000000..c85e35b Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00065.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00066.html b/documentation/html/stories/TheOtherOrchard/images/00066.html new file mode 100644 index 0000000..af474e4 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00066.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00066.png b/documentation/html/stories/TheOtherOrchard/images/00066.png new file mode 100644 index 0000000..048c27f Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00066.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00067.html b/documentation/html/stories/TheOtherOrchard/images/00067.html new file mode 100644 index 0000000..83f3253 --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00067.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00067.png b/documentation/html/stories/TheOtherOrchard/images/00067.png new file mode 100644 index 0000000..03f5007 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00067.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00068.html b/documentation/html/stories/TheOtherOrchard/images/00068.html new file mode 100644 index 0000000..a7b3cbe --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00068.html @@ -0,0 +1,91 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00068.png b/documentation/html/stories/TheOtherOrchard/images/00068.png new file mode 100644 index 0000000..3990d4a Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00068.png differ diff --git a/documentation/html/stories/TheOtherOrchard/images/00069.html b/documentation/html/stories/TheOtherOrchard/images/00069.html new file mode 100644 index 0000000..a569f0d --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/images/00069.html @@ -0,0 +1,85 @@ + + + + + + +Sleep Is Death Flip Book + + + + + + + + + + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + +
    + + + + + + + + + + + +
    + + + + + + + + + +
    + + + + + + + + \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/images/00069.png b/documentation/html/stories/TheOtherOrchard/images/00069.png new file mode 100644 index 0000000..81fd27e Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/images/00069.png differ diff --git a/documentation/html/stories/TheOtherOrchard/index.php b/documentation/html/stories/TheOtherOrchard/index.php new file mode 100644 index 0000000..dd0c52b --- /dev/null +++ b/documentation/html/stories/TheOtherOrchard/index.php @@ -0,0 +1,249 @@ +"; + + + +// clicking on image goes to next + +if( $next != -1 ) { + + echo ""; + + } + +echo ""; + +if( $next != -1 ) { + + echo ""; + + } + + + +echo ""; + + + + + +echo ""; + + + +echo ""; + +if( $prev != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + +if( $next != -1 ) { + + echo "". + + ""; + + } + +echo ""; + + + +echo ""; + + + + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + + + +if( $next != -1 ) { + + echo ""; + + } + + + + + + + +include( "footer.php" ); + + + +?> \ No newline at end of file diff --git a/documentation/html/stories/TheOtherOrchard/next.png b/documentation/html/stories/TheOtherOrchard/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/next.png differ diff --git a/documentation/html/stories/TheOtherOrchard/prev.png b/documentation/html/stories/TheOtherOrchard/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/documentation/html/stories/TheOtherOrchard/prev.png differ diff --git a/documentation/html/testDemo/index.php b/documentation/html/testDemo/index.php new file mode 100644 index 0000000..f61978e --- /dev/null +++ b/documentation/html/testDemo/index.php @@ -0,0 +1,41 @@ + + +Test Demos
    + +
    + +v14 working builds with new music editor. You need a demo code from me to run these (so they don't escape into the wild): +
    +
    +
    + + + +
    SleepIsDeath_v14a_demo_Windows.zip
    + + + + +
    +
    +
    + + + + + diff --git a/documentation/html/testDemo/youTube.png b/documentation/html/testDemo/youTube.png new file mode 100644 index 0000000..099e468 Binary files /dev/null and b/documentation/html/testDemo/youTube.png differ diff --git a/documentation/html/ticketServer/bulk.pl b/documentation/html/ticketServer/bulk.pl new file mode 100755 index 0000000..7b0141a --- /dev/null +++ b/documentation/html/ticketServer/bulk.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl -w + + +my $wgetPath = "/usr/bin/wget"; + +my $numArgs = $#ARGV + 1; + +if( $numArgs != 4 ) { + usage(); + } + +open( LIST_FILE, $ARGV[0] ) or usage(); + +print "opening $ARGV[0] for reading\n"; + +while( ) { + chomp; + print "LINE: $_\n"; + (my $email, my $name) = split( /,\W*/ ); + $name =~ s/ /\+/g; + + print " email = ($email), name = ($name)\n"; + + my $url = "http://sleepisdeath.net/ticketServer/server.php?action=sell_ticket&security_data=$ARGV[2]&email=$email&name=$name&reference=manual&tags=$ARGV[1]&security_hash=$ARGV[3]"; + print " url = $url\n"; + $result = `$wgetPath "$url" -q -O -`; + + print " result = $result\n"; + } + + + +sub usage { + print "Usage:\n"; + print " bulk.pl list_file_name tag_name security_data security_hash\n"; + print "Example:\n"; + print " bulk.pl list.txt april16 abc 9f66c4044d8f39a9c06341b70fa791f6\n"; + print "List file must have one person per line in the following format:\n"; + print " bob\@test.com, Bob Babbs\n"; + die; + } diff --git a/documentation/html/ticketServer/checkDuplicates.pl b/documentation/html/ticketServer/checkDuplicates.pl new file mode 100755 index 0000000..8cf9c43 --- /dev/null +++ b/documentation/html/ticketServer/checkDuplicates.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl -w + + +my $wgetPath = "/usr/bin/wget"; + +my $numArgs = $#ARGV + 1; + +if( $numArgs < 2 ) { + usage(); + } + +open( LIST_FILE, $ARGV[0] ) or usage(); + +print "opening $ARGV[0] for reading\n"; + +my $hits = 0; + +while( ) { + chomp; + # print "LINE: $_\n"; + (my $email, my $name) = split( /,\W*/ ); + $name =~ s/ /\+/g; + + + for( my $argIndex=1; $argIndex<$numArgs; $argIndex++ ) { + my $otherListFile = $ARGV[ $argIndex ]; + + my $result = `grep -c $email $otherListFile`; + + if( $result > 0 ) { + print "$email, $name\n"; + $hits ++; + } + } + } + +print "+++ Total hits: $hits\n"; + +sub usage { + print "Usage:\n"; + print " checkDuplicates.pl list_file_name older_list [older_list ...]\n"; + print "Example:\n"; + print " checkDuplicates.pl list.txt fullSail1/list1.txt fullSail2/list2.txt\n"; + print "List files must have one person per line in the following format:\n"; + print " bob\@test.com, Bob Babbs\n"; + die; + } diff --git a/documentation/html/ticketServer/checkLine b/documentation/html/ticketServer/checkLine new file mode 100644 index 0000000..92fb355 --- /dev/null +++ b/documentation/html/ticketServer/checkLine @@ -0,0 +1 @@ +./checkDuplicates.pl fullSail45/list45.txt fullSail44/list44.txt fullSail43/list43.txt fullSail42/list42.txt fullSail41/list41.txt fullSail40/list40.txt fullSail39/list39.txt fullSail38/list38.txt fullSail37/list37.txt fullSail36/list36.txt fullSail35/list35.txt fullSail34/list34.txt fullSail33/list33.txt fullSail32/list32.txt fullSail31/list31.txt fullSail30/list30.txt fullSail29/list29.txt fullSail28/list28.txt fullSail27/list27.txt fullSail26/list26.txt fullSail25/list25.txt fullSail24/list24.txt fullSail23/list23.txt fullSail22/list22.txt fullSail21/list21.txt fullSail20/list20.txt fullSail19/list19.txt fullSail18/list18.txt fullSail17/list17.txt fullSail16/list16.txt fullSail15/list15.txt fullSail14/list14.txt fullSail13/list13.txt fullSail12/list12.txt fullSail11/list11.txt fullSail10/list10.txt fullSail9/list9.txt fullSail8/list8.txt fullSail7/list7.txt fullSail6/list6.txt fullSail5/list5.txt fullSail4/list4.txt fullSail3/list3.txt fullSail2/list2.txt fullSail1/list1.txt \ No newline at end of file diff --git a/documentation/html/ticketServer/filterLine b/documentation/html/ticketServer/filterLine new file mode 100644 index 0000000..c70564e --- /dev/null +++ b/documentation/html/ticketServer/filterLine @@ -0,0 +1 @@ +./filterOutDuplicates.pl fullSail45/list45.txt fullSail44/list44.txt fullSail43/list43.txt fullSail42/list42.txt fullSail41/list41.txt fullSail40/list40.txt fullSail39/list39.txt fullSail38/list38.txt fullSail37/list37.txt fullSail36/list36.txt fullSail35/list35.txt fullSail34/list34.txt fullSail33/list33.txt fullSail32/list32.txt fullSail31/list31.txt fullSail30/list30.txt fullSail29/list29.txt fullSail28/list28.txt fullSail27/list27.txt fullSail26/list26.txt fullSail25/list25.txt fullSail24/list24.txt fullSail23/list23.txt fullSail22/list22.txt fullSail21/list21.txt fullSail20/list20.txt fullSail19/list19.txt fullSail18/list18.txt fullSail17/list17.txt fullSail16/list16.txt fullSail15/list15.txt fullSail14/list14.txt fullSail13/list13.txt fullSail12/list12.txt fullSail11/list11.txt fullSail10/list10.txt fullSail9/list9.txt fullSail8/list8.txt fullSail7/list7.txt fullSail6/list6.txt fullSail5/list5.txt fullSail4/list4.txt fullSail3/list3.txt fullSail2/list2.txt fullSail1/list1.txt \ No newline at end of file diff --git a/documentation/html/ticketServer/filterOutDuplicates.pl b/documentation/html/ticketServer/filterOutDuplicates.pl new file mode 100755 index 0000000..26b1af0 --- /dev/null +++ b/documentation/html/ticketServer/filterOutDuplicates.pl @@ -0,0 +1,52 @@ +#!/usr/bin/perl -w + + +my $wgetPath = "/usr/bin/wget"; + +my $numArgs = $#ARGV + 1; + +if( $numArgs < 2 ) { + usage(); + } + +open( LIST_FILE, $ARGV[0] ) or usage(); + +# print "opening $ARGV[0] for reading\n"; + +my $hits = 0; + +while( ) { + chomp; + # print "LINE: $_\n"; + (my $email, my $name) = split( /,\W*/ ); + #$name =~ s/ /\+/g; + + + my $thisHitCount = 0; + for( my $argIndex=1; $argIndex<$numArgs; $argIndex++ ) { + my $otherListFile = $ARGV[ $argIndex ]; + + my $result = `grep -c $email $otherListFile`; + + if( $result > 0 ) { + $hits ++; + $thisHitCount++; + } + } + if( $thisHitCount == 0 ) { + # not a repeat + print "$email, $name\n"; + } + + } + + +sub usage { + print "Usage:\n"; + print " filterOutDuplicates.pl list_file_name older_list [older_list ...]\n"; + print "Example:\n"; + print " filterOutDuplicates.pl list.txt fullSail1/list1.txt fullSail2/list2.txt\n"; + print "List files must have one person per line in the following format:\n"; + print " bob\@test.com, Bob Babbs\n"; + die; + } diff --git a/documentation/html/ticketServer/footer.php b/documentation/html/ticketServer/footer.php new file mode 100644 index 0000000..79978e4 --- /dev/null +++ b/documentation/html/ticketServer/footer.php @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/documentation/html/ticketServer/header.php b/documentation/html/ticketServer/header.php new file mode 100644 index 0000000..89eeccc --- /dev/null +++ b/documentation/html/ticketServer/header.php @@ -0,0 +1,9 @@ + + + +Sleep Is Death (Geisterfahrer) + + + + diff --git a/documentation/html/ticketServer/makeList.sh b/documentation/html/ticketServer/makeList.sh new file mode 100755 index 0000000..1ba4ba7 --- /dev/null +++ b/documentation/html/ticketServer/makeList.sh @@ -0,0 +1,45 @@ + + +if [[ "$#" < "2" ]] ; then + echo "Usage: makeList.sh listNumber listSourceFile.txt" + echo "Example: makeList.sh 36 sidSeptember.txt" + exit +fi + +mkdir "fullSail$1" + +cp "$2" "fullSail$1/list$1.txt" + +dos2unix "fullSail$1/list$1.txt" + + +checkLineString="./checkDuplicates.pl " +filterLineString="./filterOutDuplicates.pl " + +for (( c=$1; c>=1; c-- )) +do + checkLineString="$checkLineString fullSail$c/list$c.txt" + filterLineString="$filterLineString fullSail$c/list$c.txt" +done + + + +echo -n $checkLineString > checkLine +echo -n $filterLineString > filterLine + +sh filterLine > "fullSail$1/list$1_filtered.txt" + + +tag="fullSailClass$1" + +echo + +echo "Make sure that $tag exists as a tag on the server." + +echo + +echo -n "Then paste server hash of 'abc' here: " + +read hash + +./bulk.pl "fullSail$1/list$1_filtered.txt" "$tag" abc "$hash" \ No newline at end of file diff --git a/documentation/html/ticketServer/settings.php b/documentation/html/ticketServer/settings.php new file mode 100644 index 0000000..3cdce46 --- /dev/null +++ b/documentation/html/ticketServer/settings.php @@ -0,0 +1,99 @@ + "2010-04-09 00:00:00", + "april_12" => "2010-04-12 00:00:00" ); + +// secret shared with fastspring server, one per purchase tag from above +$fastspringPrivateKeys = array( "april_9" => "secret A", + "april_12" => "secret B" ); + + +// files to serve, from path below +$fileList = array( "sueFamilyWeb.jpg", "SleepIsDeath_v2_UnixSource.tar.gz" ); + +$fileDescriptions = array( + "Sue's family picture, from the archive.", + "Unix (and Mac/Windows) Source Code (notes)" ); + + + +// should not be web-accessible +$downloadFilePath = "/home/jcr13/sidDownloads/"; + + + +// header and footers for various pages +$header = "include( \"header.php\" );"; +$footer = "include( \"footer.php\" );"; + +$fileListHeader = $header . +"echo \"
    Downloads


    \";"; + + + + +// parameters for download emails that are sent out +$siteName = "Sleep Is Death"; +$siteEmailAddress = "jcr13@cornell.edu"; + + + +// number of tickets shown per page in the browse view +$ticketsPerPage = 6; + + + + + +?> \ No newline at end of file diff --git a/documentation/html/tieBuster.sk b/documentation/html/tieBuster.sk new file mode 100644 index 0000000..e63e530 --- /dev/null +++ b/documentation/html/tieBuster.sk @@ -0,0 +1,30 @@ +##Sketch 1 2 +document() +layout('A4',0) +layer('Layer 1',1,1,0,0,(0,0,0)) +fp((0,0,0)) +lw(1) +r(308.126,0,0,-277.959,97.2484,675.202) +lp((1,0,0)) +lw(15) +b() +bs(281.477,507.134,0) +bs(212.526,582.549,0) +lp((1,0,0)) +lw(15) +e(57.1002,0,0,-57.1002,247.002,544.437) +fp((1,1,1)) +le() +lw(1) +b() +bs(234.612,599.652,0) +bs(259.93,599.652,0) +bs(251.311,584.569,0) +bs(259.391,506.46,0) +bs(247.54,489.223,0) +bs(234.073,507.538,0) +bs(241.615,584.569,0) +bs(234.612,599.652,0) +bC() +guidelayer('Guide Lines',1,0,0,1,(0,0,1)) +grid((0,0,20,20),0,(0,0,1),'Grid') diff --git a/documentation/html/twitter.png b/documentation/html/twitter.png new file mode 100644 index 0000000..08104ef Binary files /dev/null and b/documentation/html/twitter.png differ diff --git a/documentation/html/v14music.png b/documentation/html/v14music.png new file mode 100644 index 0000000..755e5d9 Binary files /dev/null and b/documentation/html/v14music.png differ diff --git a/documentation/html/videos.php b/documentation/html/videos.php new file mode 100644 index 0000000..2a7f94b --- /dev/null +++ b/documentation/html/videos.php @@ -0,0 +1,73 @@ + + +Tutorial Videos
    +
    + + +Tutorial 1: Controller Basics
    +
    + +
    + +
    +
    +Tutorial 2: Room Editor Basics
    +
    + +
    + +
    +
    +Tutorial 3: Advanced Room Editor Usage
    +
    + +
    + + +
    +
    +Tutorial 4: Object Editor Basics
    +
    + +
    + + +
    +
    +Tutorial 5: Advanced Object Editor Usage
    +
    + +
    + + + +
    +
    +Tutorial 6: Advanced Depth Sorting
    +
    + +
    + + +
    +
    +Tutorial 7: Final Tips
    +
    + +
    + + + + + + + + + +
    +
    +
    + + diff --git a/documentation/html/youTube1.png b/documentation/html/youTube1.png new file mode 100644 index 0000000..58b37ff Binary files /dev/null and b/documentation/html/youTube1.png differ diff --git a/documentation/html/youTube2.png b/documentation/html/youTube2.png new file mode 100644 index 0000000..1413ab9 Binary files /dev/null and b/documentation/html/youTube2.png differ diff --git a/documentation/html/youTube3.png b/documentation/html/youTube3.png new file mode 100644 index 0000000..c300933 Binary files /dev/null and b/documentation/html/youTube3.png differ diff --git a/documentation/html/youTube4.png b/documentation/html/youTube4.png new file mode 100644 index 0000000..5b0bd1b Binary files /dev/null and b/documentation/html/youTube4.png differ diff --git a/documentation/html/youTube5.png b/documentation/html/youTube5.png new file mode 100644 index 0000000..d1d2089 Binary files /dev/null and b/documentation/html/youTube5.png differ diff --git a/documentation/html/youTube6.png b/documentation/html/youTube6.png new file mode 100644 index 0000000..52eb8b2 Binary files /dev/null and b/documentation/html/youTube6.png differ diff --git a/documentation/html/youTube7.png b/documentation/html/youTube7.png new file mode 100644 index 0000000..07dd691 Binary files /dev/null and b/documentation/html/youTube7.png differ diff --git a/documentation/installation/layout.jpg b/documentation/installation/layout.jpg new file mode 100755 index 0000000..2abd65a Binary files /dev/null and b/documentation/installation/layout.jpg differ diff --git a/documentation/press/codeSheet/Makefile b/documentation/press/codeSheet/Makefile new file mode 100644 index 0000000..84b106b --- /dev/null +++ b/documentation/press/codeSheet/Makefile @@ -0,0 +1,28 @@ + + +# +# Generic: +# +# Map all .cpp C++ and C files into .o object files +# +# $@ represents the name.o file +# $< represents the name.cpp file +# +%.eps: %.png + convert $< $@ +%.eps: %.sk + sk2ps $< $@ +%.dvi: %.tex + latex $< +%.ps: %.dvi + dvips -Ppdf -t letter -o $@ $< +%.pdf: %.ps + ps2pdf $< + + +all: sheet.pdf + +clean: + rm *.pdf *.ps *.dvi *.log *.aux + + diff --git a/documentation/press/codeSheet/sheet.dvi b/documentation/press/codeSheet/sheet.dvi new file mode 100644 index 0000000..794d5b0 Binary files /dev/null and b/documentation/press/codeSheet/sheet.dvi differ diff --git a/documentation/press/interviews/gamesInterview.txt b/documentation/press/interviews/gamesInterview.txt new file mode 100644 index 0000000..6ab50a2 --- /dev/null +++ b/documentation/press/interviews/gamesInterview.txt @@ -0,0 +1,79 @@ +> You’re well known as a maker of art games – titles that use metaphor +> and mechanics to impart themes and ideas. Yet Sleep Is Death is far +> more about play and interactions between players. Why make such a +> different game? + +After making eleven games in the proceduralist style, I started to feel the limitations pretty sharply. I was stuck with symbols of people and relationships, not characters and conversations. Symbols can be powerful, but they are usually general. A lot of the things that I wanted to express were much more specific. Grandparents. Bullies. Girlfriends. Not symbols of these characters, but the actual people. And I don't mean that I wanted to tell stories, because these real-life experiences were deeply interactive. How could I make fully-interactive work that did justice to this material? + + +> Having a human brain replace AI is a rather elegant solution to the +> problems faced by games like Façade. What inspirations and +> experiences led you to think of this solution? + +I kept throwing away game design topics whenever I realized that they involved characters and dialog. Dialog in games is a really hard unsolved problem. Some very smart people, like Chris Crawford, have been working on it for decades. Facade may be our best working example, and that's five years old. So there is essentially no progress being made in this seemingly-important area, and some experts predict that we're still 20 years away. Yikes! I'm not going to throw decades of my life at this potentially unsolvable problem. On the other hand, I don't want wait 20 years to make games about the topics that really interest me. Sleep Is Death is my temporary solution. It gives you meaningful character interactions in a video game. Today. + + +> Could you ever see Sleep Is Death becoming a bigger, more consumer- +> friendly product? Perhaps making its way into the mainstream a la +> LittleBigPlanet? + +That would be hard to imagine for several reasons. Games like Little Big Planet and Spore have interactions between players, but it's mostly asynchronous---stuff is posted by one person to be consumed by others later. Sleep Is Death requires both the creator and the consumer to be connected simultaneously, which is not as convenient. + +Controlling a story-world live, while the other person is online and waiting, requires fortitude. It's a bit like being on stage, and I get something akin to "butterflies" before every live story that I tell. Even the player can experience some of this flustered tension. That can be exhilarating and deeply interesting. But like stage acting, it's probably not for everyone. + + +> You’ve described Sleep Is Death as an ‘intimate’ game. What makes it +> intimate? + +When it's done right, it is the closest thing that I've experienced to a melding of minds or a shared hallucination. The timer forces improvisation, and improvisation doesn't leave room for conscious filtering. In the moment, you say and do surprising things through the character that you are controlling. This is a game that I would not want to play with a total stranger. + + +> Sleep Is Death offers a new kind of experience – one that plays out +> more like improv theatre rather than game. Do you think people will +> ‘get’ the collaborative and make-believe aspects of play, or will +> they struggle with something they don’t understand? + +I think that people generally get it. On the Player end, Sleep Is Death offers a game experience that most game-players have been dreaming about for a long time---a game where you can say and do whatever you want. That experience acts as a pretty friendly gateway, though the novelty of doing anything you want wears off pretty quickly. Then you start thinking about characters, and how to play characters in a way that makes an interesting story. + + + +> In all of your games with other players, has anyone ever done +> anything that totally took you by surprise? + +All the time. That's the part of the point. The really good players, however, have done surprising things that moved the story in an interesting direction. Cutting open a cactus during a water shortage, not trying to ride a dog like a horse for no reason. + + +> How much preparation time do you need to put into the creation of a +> story? + +It depends on how much time I want to spend on the visuals. Some story-worlds, with more polished visuals, have taken me about five hours to prepare. If I'm cobbling a story-world together out of existing bits and pieces, or I'm using simpler visuals, it only takes me an hour or two. + + +> On the phone we talked a little about how making more complex games +> aesthetically means it's more difficult to make them interactive. Do +> you think this could be an issue in the future, or do you think the +> sharing of assets between players will result in larger, more complex +> stories further down the line? + +There's a fundamental trade-off between visual detail and interactivity. The more detailed something is, the harder it is to edit as needed in 30 seconds. For example, if you design an object with realistic highlights and shadows, what happens when the object gets knocked over or broken in half? Suddenly, the highlights and shadows are off, and there's no way to fix them in 30 seconds. Sleep Is Death has the visual style that it does---simple, low-res pixel graphics---for a good reason. + + +> Do you think we'll ever reach a point where AI will be able to take +> over from a human in Sleep Is Death, without the other player +> perceiving the difference? + +I've been waiting about 27 years for this to happen. I'm still waiting. I hope it will happen in my lifetime, but I'm skeptical. + + + +> Sleep Is Death provides a new way of imparting drama without the need +> for violence. Do you think other developers will pick up on this? +> What impact do you hope Sleep Is Death will have on games in the future? + +I don't know what stories you've been playing, but almost all of mine have violence! + +Sleep Is Death is such a specific system design, where all of my core design choices were necessary for it work at all. It's necessarily low-res, turn-based, editor-heavy, and typing-heavy. I have trouble imagining the core idea being expanded into other games. How could you make a realtime version? How could you make a 3D version? A voiced version? + +However, I do see "natural" intelligence popping up more and more in mainstream games. Player-created creatures in Spore. Friends controlling zombies in Left4Dead. Player-controlled monsters in Demon's Souls. + +The core insight for all of these developments is that playing as the AI can be even more interesting than playing as the player. diff --git a/documentation/press/shots/controllerScreen.png b/documentation/press/shots/controllerScreen.png new file mode 100644 index 0000000..bd6c46b Binary files /dev/null and b/documentation/press/shots/controllerScreen.png differ diff --git a/documentation/press/shots/playerScreen.png b/documentation/press/shots/playerScreen.png new file mode 100644 index 0000000..da32d01 Binary files /dev/null and b/documentation/press/shots/playerScreen.png differ diff --git a/documentation/releasePlan/calendar.png b/documentation/releasePlan/calendar.png new file mode 100644 index 0000000..7e9c1a4 Binary files /dev/null and b/documentation/releasePlan/calendar.png differ diff --git a/documentation/releasePlan/calendar.sk b/documentation/releasePlan/calendar.sk new file mode 100644 index 0000000..53d9d11 --- /dev/null +++ b/documentation/releasePlan/calendar.sk @@ -0,0 +1,346 @@ +##Sketch 1 2 +document() +layout('A4',0) +layer('Layer 1',1,1,0,0,(0,0,0)) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,140,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,200,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,260,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,320,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,380,580) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,380,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,440,580) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,440,580) +fp((0.886,0.886,0.886)) +lw(1) +r(60,0,0,-300,500,580) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,500,580) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,140,520) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,200,520) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,260,520) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,320,520) +fp((0.919,0.655,0.253)) +lw(1) +r(60,0,0,-60,380,520) +lw(1) +r(420,0,0,-60,140,580) +lw(1) +r(420,0,0,-60,140,520) +lw(1) +r(420,0,0,-60,140,460) +lw(1) +r(420,0,0,-60,140,400) +lw(1) +r(420,0,0,-60,140,340) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('2',(440,560)) +fp((0.436,0.309,0.117)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('Tickets Sold',(400,540)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('5',(200,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('12',(200,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('19',(200,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('26',(200,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('8',(380,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('15',(380,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('22',(380,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('29',(380,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('1',(380,560)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('4',(140,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('11',(140,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('18',(140,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('25',(140,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('7',(320,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('14',(320,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('21',(320,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('28',(320,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('3',(500,560)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('6',(260,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('13',(260,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('20',(260,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('27',(260,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('9',(440,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('16',(440,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('23',(440,380)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('30',(440,320)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('10',(500,500)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('April 2010',(140,585.208)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('17',(500,440)) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('24',(500,380)) +G() +lp((0,0.872,0.082)) +lw(5) +e(20,0,0,-20,470,490) +fp((0.015,0.477,0)) +lp((0,0.872,0.082)) +lw(5) +Fn('Times-Roman') +Fs(24) +txt('$40',(452,481.908)) +G_() +G() +lp((0,0.872,0.082)) +lw(5) +e(20,0,0,-20,290,430) +fp((0.015,0.477,0)) +lp((0,0.872,0.082)) +lw(5) +Fn('Times-Roman') +Fs(24) +txt('$30',(272,421.908)) +G_() +G() +lp((0,0.872,0.082)) +lw(5) +e(20,0,0,-20,230,370) +fp((0.015,0.477,0)) +lp((0,0.872,0.082)) +lw(5) +Fn('Times-Roman') +Fs(24) +txt('$15',(212,361.908)) +G_() +G() +lp((0,0.872,0.082)) +lw(5) +e(20,0,0,-20,470,370) +fp((0.015,0.477,0)) +lp((0,0.872,0.082)) +lw(5) +Fn('Times-Roman') +Fs(24) +txt('$10',(452,361.908)) +G_() +G() +lp((0,0.872,0.082)) +lw(5) +e(20,0,0,-20,470,310) +fp((0.015,0.477,0)) +lp((0,0.872,0.082)) +lw(5) +Fn('Times-Roman') +Fs(24) +txt('$ 7',(452,301.908)) +G_() +lp((0.435,0.306,0.114)) +lw(2) +la1(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1)) +b() +bs(360,530,0) +bs(420,530,0) +lp((0.435,0.306,0.114)) +lw(2) +la1(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1)) +b() +bs(550,530,0) +bs(490,530,0) +lp((0.435,0.306,0.114)) +lw(2) +la1(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1)) +b() +bs(390,470,0) +bs(170,470,0) +G() +fp((0.436,0.309,0.117)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('Last day',(0.524691,0,0,0.52469,396.984,501.399)) +fp((0.436,0.309,0.117)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('to buy',(0.524691,0,0,0.52469,396.984,490.066)) +fp((0.436,0.309,0.117)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('tickets',(0.524691,0,0,0.52469,396.984,478.733)) +G_() +guidelayer('Guide Lines',1,0,0,1,(0,0,1)) +grid((0,0,10,10),0,(0,0,1),'Grid') diff --git a/documentation/releasePlan/plan.txt b/documentation/releasePlan/plan.txt new file mode 100644 index 0000000..39e6c64 --- /dev/null +++ b/documentation/releasePlan/plan.txt @@ -0,0 +1,78 @@ +In the world of digital distribution and sales, we have several problems: + +--One-size-fits-all pricing for downloads---too expensive for some and too cheap for others---reduces our potential audience while not getting enough revenue from the already-dedicated audience. + +--The potential for "leaks" (or even good-natured, friend-to-friend sharing) that subvert download sales. + +--The use of DRM and other value-detractors to deal with the previous problem. + +--Reliance on guilt as a motivator for sales ("I could get it for free from my friend, but I want to support the artist, so I'll pay." You don't pay $249 to see Clapton at Madison Square because you want to support Clapton.) + +--Tendency to rely on sales of peripheral, non-digital "stuff" (t-shirts, special boxed editions, posters) when our primary busy-ness is making digital stuff. + + + +The source of the above problems is the same: lack of natural scarcity. A copy of a digital game, after it has been released, will never again be scarce. However, it may be possible to introduce a natural scarcity. Don't sell copies of the game. Sell the *experience of getting the game* (on a particular day, in a particular way) instead. That experience cannot be reproduced on the PirateBay or given to a friend. + +There are a bunch of ways to do this. Below is a description of one way that I'm planning on trying for the release of Sleep Is Death. + + + + +Instead of selling copies after the game has been released, I will be selling *tickets* for exclusive access to the game before it has been released. Because tickets are sold ahead of time, before the game has been downloaded by anyone at all, there is no chance of a "leak" or alternative release channel subverting ticket sales. If you want to ensure that you get a copy of the game on the first weekend, there's only one way to do that---buy a ticket. + +Tickets will be on sale until Midnight, New York time, on the night of Thursday, April 8. + + +Variable ticket prices will allow you to download the game on different dates, with more expensive tickets giving your earlier access. + +But you MUST buy a ticket ahead of time (before April 9) to be able to download the game from me in April. No additional tickets will be sold once the game has been released to the first group of ticket-holders. + + +Ticket price: Download date: +$40 Friday, April 9 (1st weekend) +$30 Tuesday, April 13 (1st week, missing Monday) +$15 Monday, April 19 (2nd week) +$10 Friday, April 23 (3rd weekend) +$ 7 Friday, April 30 (4th weekend, 3 weeks after release) + + +A ticket buys you access to all of the following DRM-free downloads: +--Windows build. +--MacOS build. +--Full source code bundle (which can also be compiled on GNU/Linux) + + + + +Internal notes: + +Depending on how this goes, I will be releasing the game to the rest of the public on Friday, May 14, two weeks after the last ticket-holders get it. If ticket sales are sufficient, I'll probably release it for free on SourceForge, where it will be maintained in perpetuity. + +If ticket sales are not sufficient, I may try to take advantage of the long interest-tail by selling downloads directly for $5 (or maybe for more than the lowest rung?) + + +Journalists will be provided with demo copies of the game throughout March, leading up to the final days of ticket sales. + + + + + + +Update: + +Terry got a lot of blow-back about VVVVVV being $15. Jon got similar flack for the initial price of Braid (also $15). Don't want to turn off any potential buyers, don't want to even *appear* greedy. Also, worried that the vast majority of people will want it at the lower price... don't want them to feel punished by making them wait 3 weeks. Yes, the $40 people feel rewarded, but how many of those will there be? Make 5 people happy, piss of 1000s of people. Also, want to keep it as simple as possible while still gathering valuable data. 5 price points increases weight of decision too much. + +The core question: are people willing to pay more for "special" access to a game? How many people are willing to pay? + +Also, the idea of eBay sales of a few "really early" downloads came up. Maybe 5 downloads, one day earlier than everyone else. That would be a good way to measure the true market price for early access. + +Also, it's interesting to think about making the "upgrade" to an earlier release small enough that it is attractive to most people. "Just a few more dollars, might as well." + +If it is double the price, it might give people pause. If it crosses the $10 mark, it will give people pause. Maybe $6 and $9? $7 and $9? $7 and $11... $8 and $11 + + +Ticket price: Download date: +5 eBay sales Thursday, April 8 (1 day early) +$11 Friday, April 9 (1st weekend) +$ 7 Monday, April 12 (1st week) \ No newline at end of file diff --git a/documentation/teaser/description.txt b/documentation/teaser/description.txt new file mode 100644 index 0000000..30db9cf --- /dev/null +++ b/documentation/teaser/description.txt @@ -0,0 +1,5 @@ +Title: +Sleep is Death (Geisterfahrer) + +Description: +An asymmetric game for two players about a story. \ No newline at end of file diff --git a/documentation/teaser/screen.png b/documentation/teaser/screen.png new file mode 100644 index 0000000..850a221 Binary files /dev/null and b/documentation/teaser/screen.png differ diff --git a/gameSource/BorderPanel.cpp b/gameSource/BorderPanel.cpp new file mode 100644 index 0000000..a8f4a96 --- /dev/null +++ b/gameSource/BorderPanel.cpp @@ -0,0 +1,34 @@ +#include "BorderPanel.h" + + + + +BorderPanel::BorderPanel( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Color *inColor, Color *inBorderColor, + double inBorderWidth ) + // make panel contents smaller + : GUIPanelGL( inAnchorX + inBorderWidth, inAnchorY + inBorderWidth, + inWidth - 2 * inBorderWidth, inHeight - 2 * inBorderWidth, + inColor ), + // draw the exteral panel to our full dimensions + mBorder( new GUIPanelGL( inAnchorX, inAnchorY, inWidth, inHeight, + inBorderColor ) ) { + + } + + + +BorderPanel::~BorderPanel() { + delete mBorder; + } + + + +void BorderPanel::fireRedraw() { + + mBorder->fireRedraw(); + + // superclass redraw to fill contents + GUIPanelGL::fireRedraw(); + } diff --git a/gameSource/BorderPanel.h b/gameSource/BorderPanel.h new file mode 100644 index 0000000..c2dfe03 --- /dev/null +++ b/gameSource/BorderPanel.h @@ -0,0 +1,52 @@ +#ifndef BORDER_PANEL_INCLUDED +#define BORDER_PANEL_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" + +#include + + +/** + * A panel with a colored border of a given width. + * + * @author Jason Rohrer + */ +class BorderPanel : public GUIPanelGL { + + + public: + + + + /** + * Constructs a panel. + * + * Parameters are same as for GUIPanelGL. + */ + BorderPanel( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Color *inColor, Color *inBorderColor, + double inBorderWidth ); + + + + virtual ~BorderPanel(); + + + + // override fireRedraw() in GUIPanelGL + virtual void fireRedraw(); + + protected: + + GUIPanelGL *mBorder; + + + }; + + + +#endif + + + diff --git a/gameSource/ColorEditor.cpp b/gameSource/ColorEditor.cpp new file mode 100644 index 0000000..9d04f2e --- /dev/null +++ b/gameSource/ColorEditor.cpp @@ -0,0 +1,299 @@ +#include "ColorEditor.h" +#include "ColorWells.h" +#include "BorderPanel.h" + + +#include "minorGems/graphics/openGL/gui/LabelGL.h" + + +extern TextGL *largeText; + +extern ColorWells *mainColorStack; + + + +ColorEditor::ColorEditor( ScreenGL *inScreen ) + : Editor( inScreen, false ), + mEditPalettePressed( false ), + mIgnoreEvent( false ) { + + mCloseButton->setToolTip( "tip_closeEdit_color" ); + + + mSidePanel->add( mainColorStack ); + + mainColorStack->addActionListener( this ); + + + Color *thumbColor = new Color( .5, .5, .5, .5 ); + Color *borderColor = new Color( .35, .35, .35, .35 ); + + + mValueSlider = new ToolTipSliderGL( 248, 188, + 64, 12, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 0, 1, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + + mSaturationSlider = new ToolTipSliderGL( 248, 175, + 64, 12, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 0, 1, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + + delete thumbColor; + delete borderColor; + + mSidePanel->add( mValueSlider ); + mSidePanel->add( mSaturationSlider ); + + mValueSlider->setToolTip( "tip_valueSlider" ); + mSaturationSlider->setToolTip( "tip_saturationSlider" ); + + + + mHVPicker = new HueValuePicker( 248, 203, + 64, 64 ); + + mSidePanel->add( mHVPicker ); + + + rgbaColor c = mainColorStack->getSelectedColor(); + + mWorkingColor = new Color( c.comp.r / 255.0, + c.comp.g / 255.0, + c.comp.b / 255.0, + 1.0 ); + + float h, s, v; + + mWorkingColor->makeHSV( &h, &s, &v ); + + // round to increments of 128 pixels to avoid round-off artifacts + mValueSlider->setThumbPosition( (int)(v * 128) / 128.0 ); + mSaturationSlider->setThumbPosition( (int)(s * 128) / 128.0 ); + + mHVPicker->setValues( h, v ); + + adjustBarColors(); + + mValueSlider->addActionListener( this ); + mSaturationSlider->addActionListener( this ); + mHVPicker->addActionListener( this ); + + + mEditPaletteButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 51, + 8, + 8 ); + + mSidePanel->add( mEditPaletteButton ); + + mEditPaletteButton->addActionListener( this ); + mEditPaletteButton->setToolTip( "tip_edit_palette" ); + + + postConstruction(); + } + + + +ColorEditor::~ColorEditor() { + mSidePanel->remove( mainColorStack ); + + delete mWorkingColor; + } + + + +void ColorEditor::setEditPaletteButtonVisible( char inIsVisible ) { + mEditPaletteButton->setEnabled( inIsVisible ); + } + + + +void ColorEditor::adjustBarColors() { + Color *valStart = Color::makeColorFromHSV( + mHVPicker->getSelectedHue(), + mSaturationSlider->getThumbPosition(), + 0 ); + + Color *valEnd = Color::makeColorFromHSV( + mHVPicker->getSelectedHue(), + mSaturationSlider->getThumbPosition(), + 1 ); + + mValueSlider->setBarStartColor( valStart ); + mValueSlider->setBarEndColor( valEnd ); + + Color *satStart = Color::makeColorFromHSV( + mHVPicker->getSelectedHue(), + 0, + mHVPicker->getSelectedValue() ); + + + Color *satEnd = Color::makeColorFromHSV( + mHVPicker->getSelectedHue(), + 1, + mHVPicker->getSelectedValue() ); + + mSaturationSlider->setBarStartColor( satStart ); + mSaturationSlider->setBarEndColor( satEnd ); + } + + + +char ColorEditor::getDragging() { + return + mHVPicker->mPressed || + mSaturationSlider->mDragging || + mValueSlider->mDragging; + } + + + +void ColorEditor::actionPerformed( GUIComponent *inTarget ) { + + if( mIgnoreEvent ) { + return; + } + + // superclass + Editor::actionPerformed( inTarget ); + + if( inTarget == mEditPaletteButton ) { + // close ourself + mEditPalettePressed = true; + mCloseButton->fireActionPerformed( mCloseButton ); + mEditPalettePressed = false; + } + + + char colorChange = false; + + + if( inTarget == mSaturationSlider || + inTarget == mHVPicker ) { + + float h = mHVPicker->getSelectedHue(); + float s = mSaturationSlider->getThumbPosition(); + float v = mHVPicker->getSelectedValue(); + + + mIgnoreEvent = true; + + mValueSlider->setThumbPosition( (int)( v* 128 ) / 128.0f); + + mIgnoreEvent = false; + + + Color *newWorking = Color::makeColorFromHSV( h, s, v ); + + mWorkingColor->setValues( newWorking ); + delete newWorking; + + mIgnoreEvent = true; + addColor(); + mIgnoreEvent = false; + + colorChange = true; + } + + + if( inTarget == mValueSlider ) { + + + float h = mHVPicker->getSelectedHue(); + float s = mSaturationSlider->getThumbPosition(); + float v = mValueSlider->getThumbPosition(); + + //normalize( h, s, v, &h, &s, &v ); + + + mIgnoreEvent = true; + mHVPicker->setValues( h, v ); + mIgnoreEvent = false; + + + + Color *newWorking = Color::makeColorFromHSV( h, s, v ); + + mWorkingColor->setValues( newWorking ); + delete newWorking; + + + + // ignore color change to prevent sliders from jumping + // (each HSV is not a unique RGB) + mIgnoreEvent = true; + addColor(); + mIgnoreEvent = false; + + colorChange = true; + } + + + if( inTarget == mainColorStack ) { + // new color picked on stack + rgbaColor c = mainColorStack->getSelectedColor(); + + mWorkingColor->r = c.comp.r / 255.0f; + mWorkingColor->g = c.comp.g / 255.0f; + mWorkingColor->b = c.comp.b / 255.0f; + + + + float h, s, v; + mWorkingColor->makeHSV( &h, &s, &v ); + + + mIgnoreEvent = true; + + // round to increments of 128 pixels to avoid round-off artifacts + mValueSlider->setThumbPosition( (int)( v* 128 ) / 128.0f); + mSaturationSlider->setThumbPosition( (int)( s* 128 ) / 128.0f); + mHVPicker->setValues( h, v ); + + mIgnoreEvent = false; + + colorChange = true; + } + + + if( colorChange ) { + adjustBarColors(); + } + + } + + + +void ColorEditor::editorClosing() { + addColor(); + } + + + +void ColorEditor::addColor() { + rgbaColor c; + + c.comp.r = (unsigned char)( mWorkingColor->r * 255 ); + c.comp.g = (unsigned char)( mWorkingColor->g * 255 ); + c.comp.b = (unsigned char)( mWorkingColor->b * 255 ); + c.comp.a = 255; + + // replace current well + mainColorStack->pushColor( c, true ); + } + + diff --git a/gameSource/ColorEditor.h b/gameSource/ColorEditor.h new file mode 100644 index 0000000..40354c3 --- /dev/null +++ b/gameSource/ColorEditor.h @@ -0,0 +1,63 @@ +#ifndef COLOR_EDITOR_INCLUDED +#define COLOR_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "HueValuePicker.h" + +#include "ToolTipSliderGL.h" + + +class ColorEditor : public Editor { + + public: + + ColorEditor( ScreenGL *inScreen ); + + ~ColorEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + // triggered by add button or close + // can be called externally to force edited color to top of stack + void addColor(); + + + // true if controls are currently being dragged + char getDragging(); + + // true if closed due to press of edit palette + char mEditPalettePressed; + + + void setEditPaletteButtonVisible( char inIsVisible ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + + void adjustBarColors(); + + + + ToolTipSliderGL *mValueSlider; + + ToolTipSliderGL *mSaturationSlider; + + HueValuePicker *mHVPicker; + + + Color *mWorkingColor; + + EditButtonGL *mEditPaletteButton; + + + char mIgnoreEvent; + }; + +#endif diff --git a/gameSource/ColorWells.cpp b/gameSource/ColorWells.cpp new file mode 100644 index 0000000..32d0856 --- /dev/null +++ b/gameSource/ColorWells.cpp @@ -0,0 +1,446 @@ +#include "ColorWells.h" + +#include "ColorEditor.h" + + +extern ColorEditor *mainColorEditor; + + + +Color selectedBorder( 1, 1, 1, 1 ); + +Color unselectedBorder( 0.35, 0.35, 0.35, 1 ); + + +static const char *wellFileName = "palette.txt"; + + + +ColorWellButtonGL::ColorWellButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ToolTipButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mSelected( false ), mColor( 0, 0, 0, 1 ) { + + + } + + + + + +void ColorWellButtonGL::setColor( rgbaColor inColor ) { + mColor.setValues( inColor.comp.r / 255.0f, + inColor.comp.g / 255.0f, + inColor.comp.b / 255.0f, + inColor.comp.a / 255.0f ); + + + } + + +void ColorWellButtonGL::setSelected( char inSelected ) { + mSelected = inSelected; + } + + + +void ColorWellButtonGL::mouseDragged( double inX, double inY ) { + ToolTipButtonGL::mouseDragged( inX, inY ); + + if( isEnabled() && isInside( inX, inY ) ) { + // fire an event + fireActionPerformed( this ); + } + } + + +void ColorWellButtonGL::drawPressed() { + + Color *borderColor = &unselectedBorder; + + if( mSelected ) { + borderColor = &selectedBorder; + } + + // 1 pixel wide + glColor4f( borderColor->r, + borderColor->g, + borderColor->b, 1 ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + // black fill + glColor4f( 0, 0, 0, 1 ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX + 1, mAnchorY + 1 ); + glVertex2d( mAnchorX + mWidth - 1, mAnchorY + 1 ); + glVertex2d( mAnchorX + mWidth - 1, mAnchorY + mHeight - 1 ); + glVertex2d( mAnchorX + 1 , mAnchorY + mHeight - 1 ); + } + glEnd(); + + + // center color block + glColor4f( mColor.r, + mColor.g, + mColor.b, 1); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX + 2, mAnchorY + 2 ); + glVertex2d( mAnchorX + mWidth - 2, mAnchorY + 2 ); + glVertex2d( mAnchorX + mWidth - 2, mAnchorY + mHeight - 2 ); + glVertex2d( mAnchorX + 2 , mAnchorY + mHeight - 2 ); + } + glEnd(); + } + + +void ColorWellButtonGL::drawUnpressed() { + drawPressed(); + } + + + + + + + +ColorWells::ColorWells( double inAnchorX, double inAnchorY ) + : BorderPanel( inAnchorX, inAnchorY,50, 124, + new Color( 0, 0, 0, 1 ), + new Color( 0.35, 0.35, 0.35, 1 ), + 1 ), + mLastActionWellChange( false ) { + + int buttonW = 10; + + + FILE *f = fopen( wellFileName, "r" ); + + SimpleVector savedColors; + + if( f != NULL ) { + + rgbaColor c; + c.comp.a = 255; + + int numRead = 1; + + while( numRead == 1 ) { + int r, g, b; + + numRead = fscanf( f, "%d", &r ); + numRead = fscanf( f, "%d", &g ); + numRead = fscanf( f, "%d", &b ); + + c.comp.r = (unsigned char)r; + c.comp.g = (unsigned char)g; + c.comp.b = (unsigned char)b; + + if( numRead == 1 ) { + savedColors.push_back( c ); + } + } + fclose( f ); + } + + int numSavedColors = savedColors.size(); + int nextSavedColor = 0; + + for( int y=0; yaddActionListener( this ); + + if( nextSavedColor < numSavedColors ) { + + mColorWells[y][x] = + *( savedColors.getElement( nextSavedColor ) ); + + nextSavedColor++; + } + else { + // random + mColorWells[y][x].comp.r = rand() % 255; + mColorWells[y][x].comp.g = rand() % 255; + mColorWells[y][x].comp.b = rand() % 255; + mColorWells[y][x].comp.a = 255; + } + + mColorButtons[y][x]->setColor( mColorWells[y][x] ); + mColorButtons[y][x]->setToolTip( "tip_colorWell" ); + } + } + + mSelected.y = mColorGridH - 1; + mSelected.x = 0; + + mColorButtons[mSelected.y][mSelected.x]->setSelected( true ); + + + mMainColorButton = + new ColorWellButtonGL( inAnchorX, + // right to top of panel, cover border + inAnchorY + mHeight - 2 * buttonW + 2, + mColorGridW * buttonW, + 2 * buttonW ); + add( mMainColorButton ); + + mMainColor = mColorWells[mSelected.y][mSelected.x]; + + mMainColorButton->setColor( mMainColor ); + mMainColorButton->setSelected( false ); + mMainColorButton->setToolTip( "tip_colorCurrent" ); + + // center below main color + mAddButton = new AddButtonGL( inAnchorX + (mWidth - 16)/2 + 1, + mMainColorButton->getAnchorY() - 20, + 16, 16 ); + add( mAddButton ); + mAddButton->addActionListener( this ); + + mAddButton->setToolTip( "tip_addColor" ); + } + + + + + +ColorWells::~ColorWells() { + + } + + + + +rgbaColor ColorWells::getSelectedColor() { + return mMainColor; + } + + + +#include "musicPlayer.h" + + +void ColorWells::pushColor( rgbaColor inColor, char inForceReplace ) { + mMainColorButton->setColor( inColor ); + mMainColor = inColor; + + + /* + // set timbre from this color + double coeffs[256]; + int numUsed = 0; + + + rgbaColor c = getSelectedColor(); + + float r = c.comp.r / 255.0f; + float g = c.comp.g / 255.0f; + float b = c.comp.b / 255.0f; + + + // r for damping amount for higher harmonics + // g for uniform damping of even harmonics + // b for uniform damping of odd harmonics + + numUsed = 40; + coeffs[0]=1; + + for( int i=1; isetSelected( + false ); + + mSelected.x = x; + mSelected.y = y; + + mColorButtons[mSelected.y][mSelected.x]->setSelected( + true ); + + fireActionPerformed( this ); + return; + } + } + } + } + + + // else replace current well + + mColorWells[mSelected.y][mSelected.x] = inColor; + + mColorButtons[mSelected.y][mSelected.x]->setColor( inColor ); + + fireActionPerformed( this ); + + + + + + + + } + + + +Palette ColorWells::getPalette() { + Palette p; + + for( int y=0; ysetColor( mColorWells[y][x] ); + } + } + mMainColor = mColorWells[mSelected.y][mSelected.x]; + + mMainColorButton->setColor( mMainColor ); + + mLastActionWellChange = false; + fireActionPerformed( this ); + } + + + + +void ColorWells::actionPerformed( GUIComponent *inTarget ) { + + if( inTarget == mAddButton ) { + // replace well + + mColorWells[mSelected.y][mSelected.x] = mMainColor; + + mColorButtons[mSelected.y][mSelected.x]->setColor( mMainColor ); + + + // save wells to file + FILE *f = fopen( wellFileName, "w" ); + + + if( f != NULL ) { + + for( int y=0; ygetDragging() ) { + return; + } + + + for( int y=0; ysetSelected( false ); + + mSelected.x = x; + mSelected.y = y; + + mColorButtons[mSelected.y][mSelected.x]->setSelected( true ); + + mMainColor = mColorWells[mSelected.y][mSelected.x]; + + mMainColorButton->setColor( mMainColor ); + + mLastActionWellChange = false; + fireActionPerformed( this ); + return; + } + } + } + } + + diff --git a/gameSource/ColorWells.h b/gameSource/ColorWells.h new file mode 100644 index 0000000..32add04 --- /dev/null +++ b/gameSource/ColorWells.h @@ -0,0 +1,114 @@ +#ifndef COLOR_WELLS_INCLUDED +#define COLOR_WELLS_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" +#include "minorGems/ui/event/ActionListenerList.h" +#include "minorGems/ui/event/ActionListener.h" + +#include "color.h" +#include "BorderPanel.h" +#include "buttons.h" +#include "common.h" +#include "Palette.h" + + +class ColorWellButtonGL : public ToolTipButtonGL { + + public: + ColorWellButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + void setColor( rgbaColor inColor ); + + void setSelected( char inSelected ); + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + // override to fire on drag + virtual void mouseDragged( double inX, double inY ); + + + protected: + char mSelected; + Color mColor; + + }; + + + +class ColorWells : public BorderPanel, public ActionListenerList, + public ActionListener { + + + public: + + + + /** + * Constructs a well set. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + ColorWells( double inAnchorX, double inAnchorY ); + + + + virtual ~ColorWells(); + + + + rgbaColor getSelectedColor(); + + + // jumps to color, if it exists, or replaces current well if it + // does not + // adds color to the currently selected well, replacing existing + void pushColor( rgbaColor inColor, char inForceReplace = false ); + + + Palette getPalette(); + + void setPalette( Palette inPalette ); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + char mLastActionWellChange; + + + protected: + + + + + static const int mColorGridW = 5; + static const int mColorGridH = 8; + + ColorWellButtonGL *mColorButtons[mColorGridH][mColorGridW]; + rgbaColor mColorWells[mColorGridH][mColorGridW]; + + ColorWellButtonGL *mMainColorButton; + AddButtonGL *mAddButton; + + rgbaColor mMainColor; + + + intPair mSelected; + + + }; + + + +#endif + + + diff --git a/gameSource/Connection.cpp b/gameSource/Connection.cpp new file mode 100644 index 0000000..10ce475 --- /dev/null +++ b/gameSource/Connection.cpp @@ -0,0 +1,473 @@ +#include "Connection.h" + +#include "minorGems/util/SettingsManager.h" +#include "minorGems/util/TranslationManager.h" +#include "minorGems/network/SocketClient.h" + +#include "minorGems/util/log/AppLog.h" + + +extern int autoJoinPort; + + +int Connection::getPort() { + + if( autoJoinPort != -1 ) { + return autoJoinPort; + } + + char portFound; + int port = SettingsManager::getIntSetting( "port", + &portFound ); + if( !portFound ) { + port = 7778; + } + return port; + } + + + + + +Connection::Connection() + : mSock( NULL ), + mError( false ), + mErrorString( NULL ) { + + message empty = {NULL, -1, 0, 0, 0}; + mNextOutgoingMessage = empty; + mNextIncomingMessage = empty; + + + + mServer = new SocketServer( getPort(), 1 ); + + } + + + + +// make a connection to an established server +Connection::Connection( char *inAddress ) + : mServer( NULL ), + mError( false ), + mErrorString( NULL ) { + + message empty = {NULL, -1, 0, 0, 0}; + mNextOutgoingMessage = empty; + mNextIncomingMessage = empty; + + + HostAddress h( stringDuplicate( inAddress ), getPort() ); + + // non-blocking + char timedOut; + mSock = SocketClient::connectToServer( &h, 0, &timedOut ); + + if( mSock == NULL ) { + setError( "err_failedConnect" ); + } + } + + + + +Connection::~Connection() { + if( mSock != NULL ) { + delete mSock; + } + if( mServer != NULL ) { + delete mServer; + } + if( mErrorString != NULL ) { + delete [] mErrorString; + } + } + + + +unsigned char computeBitChecksum( unsigned char inMessage[5] ) { + int sum = 0; + + for( int i=0; i<5; i++ ) { + for( int b=0; b<8; b++ ) { + sum += (int)( (inMessage[i] >> b) & 0x01 ); + } + } + return (unsigned char)sum; + } + + + + +char Connection::isConnected() { + if( mSock != NULL ) { + return mSock->isConnected() == 1; + } + return false; + } + + + + +char Connection::step() { + + if( mSock == NULL && mServer != NULL ) { + char timeout; + mSock = mServer->acceptConnection( 0, &timeout ); + + if( mSock == NULL && !timeout ) { + setError( "err_failedAccept" ); + } + return false; + } + else if( mSock != NULL && mServer == NULL ) { + if( mSock->isConnected() == 0 ) { + // working + return true; + } + else if( mSock->isConnected() == -1 ) { + setError( "err_failedConnect" ); + return false; + } + } + else if( mSock == NULL ) { + // maybe failed connect right in constructor + return false; + } + + + + // got past here, we have a connected socket + + + + if( mNextOutgoingMessage.length != -1 ) { + // try sending more of it + + + int i = mNextOutgoingMessage.nextToProcessIndex; + + /* + // header packed right into send body + + if( i == 0 && mNextOutgoingMessage.headerBytesProcessed != 5 ) { + // compose and send 5 header bytes + + int numBytes = mNextOutgoingMessage.length; + + unsigned char sendData[5]; + + sendData[0] = mNextOutgoingMessage.channel; + sendData[1] = ( numBytes >> 24 ) & 0xFF; + sendData[2] = ( numBytes >> 16 ) & 0xFF; + sendData[3] = ( numBytes >> 8 ) & 0xFF; + sendData[4] = ( numBytes ) & 0xFF; + + int j = mNextOutgoingMessage.headerBytesProcessed; + + // non-blocking, send only the remaining bytes of header + int numSent = mSock->send( &( sendData[j] ), 5 - j, false ); + + if( numSent == -1 ) { + setError( "err_failedSend" ); + } + else if( numSent > 0 ) { + mNextOutgoingMessage.headerBytesProcessed += numSent; + } + } + */ + //else { + if( true ) { + + // send data + + // non-blocking + int numSent = mSock->send( &( mNextOutgoingMessage.data[ i ] ), + mNextOutgoingMessage.length - i, + false, + false ); // realtime, no buffering + if( numSent < 0 ) { + if( numSent == -1 ) { + setError( "err_failedSend" ); + } + else { + // would block + // no more work to do right now + return false; + } + } + else { + i += numSent; + AppLog::getLog()->logPrintf( Log::DETAIL_LEVEL, + "Socket sent %d/%d bytes", + numSent, + mNextOutgoingMessage.length ); + + if( i == mNextOutgoingMessage.length ) { + // done with it! + delete [] mNextOutgoingMessage.data; + mNextOutgoingMessage.data = NULL; + mNextOutgoingMessage.length = -1; + mNextOutgoingMessage.nextToProcessIndex = 0; + mNextOutgoingMessage.headerBytesProcessed = 0; + } + else { + // more to go + mNextOutgoingMessage.nextToProcessIndex = i; + } + } + } + return true; + } + else { + // is there another in our queue that we can start sending? + if( mQueuedOutgoing.size() > 0 ) { + // grab next + message *m = mQueuedOutgoing.getElement( 0 ); + + mNextOutgoingMessage = *m; + + mQueuedOutgoing.deleteElement( 0 ); + + return true; + } + } + + + + + if( mNextIncomingMessage.length != -1 && + mNextIncomingMessage.headerBytesProcessed == 6 ) { + // try receiving more of it + + + int i = mNextIncomingMessage.nextToProcessIndex; + + // non-blocking + int numReceived = mSock->receive( &( mNextIncomingMessage.data[ i ] ), + mNextIncomingMessage.length - i, + 0 ); + + if( numReceived < 0 ) { + if( numReceived == -1 ) { + setError( "err_failedReceive" ); + } + else { + // timeout + // no more work right now + return false; + } + } + else { + i += numReceived; + + if( i == mNextIncomingMessage.length ) { + + // push it onto queue + mQueuedIncoming.push_back( mNextIncomingMessage ); + + // clear it to make room for next one + mNextIncomingMessage.data = NULL; + mNextIncomingMessage.length = -1; + mNextIncomingMessage.nextToProcessIndex = 0; + mNextIncomingMessage.headerBytesProcessed = 0; + } + else { + // more to go + mNextIncomingMessage.nextToProcessIndex = i; + } + } + return true; + } + else { + // try to receive header of next incoming + + + // fill buffer with partial stuff already stored in next message + int numBytes = mNextIncomingMessage.length; + + unsigned char recvData[6]; + + recvData[0] = mNextIncomingMessage.channel; + recvData[1] = ( numBytes >> 24 ) & 0xFF; + recvData[2] = ( numBytes >> 16 ) & 0xFF; + recvData[3] = ( numBytes >> 8 ) & 0xFF; + recvData[4] = ( numBytes ) & 0xFF; + recvData[5] = mNextIncomingMessage.bitCheckSum; + + // fetch remaining part + int j = mNextIncomingMessage.headerBytesProcessed; + + + // non-blocking, receive only the remaining bytes of header + int numReceived = mSock->receive( &( recvData[j] ), 6 - j, 0 ); + + + + if( numReceived == -1 ) { + setError( "err_failedReceive" ); + } + else if( numReceived > 0 ) { + mNextIncomingMessage.headerBytesProcessed += numReceived; + + // stick back into length field + + mNextIncomingMessage.channel = recvData[0]; + + mNextIncomingMessage.length = + recvData[1] << 24 | + recvData[2] << 16 | + recvData[3] << 8 | + recvData[4]; + mNextIncomingMessage.bitCheckSum = recvData[5]; + + + if( mNextIncomingMessage.headerBytesProcessed == 6 ) { + // done! + + // verify checksum + if( computeBitChecksum( recvData ) != + mNextIncomingMessage.bitCheckSum ) { + + AppLog::error( "Error: Message checksum failed" ); + + // clear it to make room for next one, + // to ensure that processing of this one stops + mNextIncomingMessage.data = NULL; + mNextIncomingMessage.length = -1; + mNextIncomingMessage.nextToProcessIndex = 0; + mNextIncomingMessage.headerBytesProcessed = 0; + + setError( "err_receiveCorrupted" ); + } + else { + // checksum correct + + // make a data buffer + mNextIncomingMessage.data = + new unsigned char[ mNextIncomingMessage.length ]; + } + + } + + return true; + } + } + + + + + return false; + } + + + + +void Connection::sendMessage( unsigned char *inData, int inLength, + unsigned char inChannel ) { + + // pack the header right into the message to avoid two consecutive sends + // (which interacts poorly with the Nagle algorithm) + message m = { new unsigned char[ inLength + 6 ], + inLength + 6, 0, 0, inChannel, + 0 }; // empty checksum + + // first 6 bytes of data are header + m.data[0] = m.channel; + m.data[1] = ( inLength >> 24 ) & 0xFF; + m.data[2] = ( inLength >> 16 ) & 0xFF; + m.data[3] = ( inLength >> 8 ) & 0xFF; + m.data[4] = ( inLength ) & 0xFF; + m.data[4] = ( inLength ) & 0xFF; + + m.bitCheckSum = computeBitChecksum( m.data ); + m.data[5] = m.bitCheckSum; + + // remainder are data payload + memcpy( &( m.data[6] ), inData, inLength ); + + mQueuedOutgoing.push_back( m ); + } + + + +unsigned char *Connection::receiveMessage( int *outLength, + unsigned char inChannel ) { + + int queueSize = mQueuedIncoming.size(); + + if( queueSize > 0 ) { + + // skip messages that don't match our channel + int i = 0; + char chanMatch = false; + while( !chanMatch && ichannel == inChannel ) { + chanMatch = true; + } + else { + i++; + } + } + + if( chanMatch ) { + + message *m = mQueuedIncoming.getElement( i ); + + *outLength = m->length; + + unsigned char *returnValue = m->data; + + mQueuedIncoming.deleteElement( i ); + + return returnValue; + } + else { + return NULL; + } + } + + return NULL; + } + + + + + +char Connection::isError() { + return mError; + } + + + +void Connection::clearError() { + if( mErrorString != NULL ) { + delete [] mErrorString; + } + mErrorString = NULL; + mError = false; + } + + + +char *Connection::getErrorString() { + return mErrorString; + } + + + +void Connection::setError( const char *inErrorTranslationKey ) { + mError = true; + + if( mErrorString != NULL ) { + delete [] mErrorString; + } + + mErrorString = stringDuplicate( TranslationManager::translate( + (char*)inErrorTranslationKey ) ); + } + + diff --git a/gameSource/Connection.h b/gameSource/Connection.h new file mode 100644 index 0000000..7430496 --- /dev/null +++ b/gameSource/Connection.h @@ -0,0 +1,87 @@ + +#include "minorGems/network/Socket.h" +#include "minorGems/network/SocketServer.h" +#include "minorGems/util/SimpleVector.h" + + + +typedef struct message { + unsigned char *data; + int length; + int nextToProcessIndex; + int headerBytesProcessed; + + unsigned char channel; + unsigned char bitCheckSum; + } message; + + +class Connection { + public: + + // start a server + Connection(); + + + // make a connection to an established server + Connection( char *inAddress ); + + + ~Connection(); + + + // get the port that connections use + static int getPort(); + + + char isConnected(); + + + // perform another step of any pending network operations + // returns true if work remains to be done + // returns false if client is done with all pending work + char step(); + + + + void sendMessage( unsigned char *inData, int inLength, + unsigned char inChannel=0 ); + + + unsigned char *receiveMessage( int *outLength, unsigned char inChannel=0 ); + + + char isError(); + + void clearError(); + + // destroyed internally + char *getErrorString(); + + protected: + + + SocketServer *mServer; + + Socket *mSock; + + + SimpleVector mQueuedOutgoing; + SimpleVector mQueuedIncoming; + + // in progress, not queued + message mNextOutgoingMessage; + message mNextIncomingMessage; + + unsigned char mReceivedHeader[4]; + int mNumHeaderBytesReceived; + + + + char mError; + char *mErrorString; + + void setError( const char *inErrorTranslationKey ); + + }; + diff --git a/gameSource/ControllerGame.cpp b/gameSource/ControllerGame.cpp new file mode 100644 index 0000000..86c315e --- /dev/null +++ b/gameSource/ControllerGame.cpp @@ -0,0 +1,784 @@ + + +#include "ControllerGame.h" +#include "common.h" +#include "ColorEditor.h" +#include "TileEditor.h" +#include "StateObjectEditor.h" +#include "MusicEditor.h" +#include "TimbreEditor.h" +#include "ScaleEditor.h" +#include "SongEditor.h" +#include "PaletteEditor.h" + +#include "ColorWells.h" +#include "TilePicker.h" +#include "RoomPicker.h" +#include "SpritePicker.h" +#include "StateObjectPicker.h" +#include "TimbrePicker.h" +#include "ScalePicker.h" +#include "MusicPicker.h" +#include "SongPicker.h" +#include "ScenePicker.h" +#include "PalettePicker.h" + +#include "TimerDisplay.h" +#include "WarningDisplay.h" +#include "ToolTipManager.h" + +#include "DragAndDropManager.h" + +#include "packSaver.h" +#include "resourceImporter.h" + + +#include "minorGems/system/Time.h" +#include "minorGems/util/log/AppLog.h" +#include "minorGems/util/SettingsManager.h" + + +#include "Connection.h" + + +extern Connection *connection; + +extern TimerDisplay *mainTimerDisplay; + +extern WarningDisplay *mainWarningDisplay; + + +ColorWells *mainColorStack; +TilePicker *mainTilePicker; + +ColorEditor *mainColorEditor; + +RoomPicker *mainRoomPicker; + +TileEditor *mainTileEditor; + +SpritePicker *mainSpritePicker; +SpritePicker *mainSpritePickerLower; + +StateObjectPicker *mainStateObjectPicker; + +TimbrePicker *mainTimbrePicker; +ScalePicker *mainScalePicker; +MusicPicker *mainMusicPicker; +SongPicker *mainSongPicker = NULL; + +ScenePicker *mainScenePicker; +PalettePicker *mainPalettePicker; + + +DragAndDropManager *mainDragAndDrop; + + +RoomEditor *mainRoomEditor; + +SpriteEditor *mainSpriteEditor; + +StateObjectEditor *mainStateObjectEditor; + +MusicEditor *mainMusicEditor; +TimbreEditor *mainTimbreEditor; +ScaleEditor *mainScaleEditor; +SongEditor *mainSongEditor = NULL; + +PaletteEditor *mainPaletteEditor; + + +PlayerMoveEditor *mainPracticePlayerEditor; + + + +char practiceMode = false; +char practiceStop = false; +char practicePlayerTurn = false; + + +//#include "fixOldResources.h" + + + + +ControllerGame::ControllerGame( ScreenGL *inScreen ) { + setScreen( inScreen ); + + + // don't fetch resources from network, since we're the only + // one using them + setUseNetwork( false ); + + + //printf( "**** RUNNING resource fixing script\n" ); + //fixOldResources(); + + + + char turnLengthFound = false; + mSecondsPerMove = SettingsManager::getIntSetting( "timeLimit", + &turnLengthFound ); + + if( !turnLengthFound ) { + mSecondsPerMove = 30; + } + + + mStepsPerSecond = 30; + resetTimer(); + + + + + mainColorStack = new ColorWells( 255, 44 ); + mainTilePicker = new TilePicker( 245, 48 + 128 ); + + mainColorEditor = new ColorEditor( inScreen ); + + + mainRoomPicker = new RoomPicker( 245, 48 ); + + mainSpritePicker = new SpritePicker( 245, 48 + 128 ); + mainSpritePickerLower = new SpritePicker( 245, 48 ); + + mainStateObjectPicker = new StateObjectPicker( 245, 48 + 128 ); + + mainTimbrePicker = new TimbrePicker( 245, 48 + 128 ); + + mainScalePicker = new ScalePicker( 245, 48 + 128 ); + + + mainMusicPicker = new MusicPicker( 245, 48 + 128 ); + + mainSongPicker = new SongPicker( 245, 48 ); + + + mainScenePicker = new ScenePicker( 245, 48 ); + + mainPalettePicker = new PalettePicker( 245, 48 + 128 ); + + + AppLog::info( "Importing resource cache" ); + importResources(); + AppLog::info( "Done importing resource cache" ); + + + AppLog::info( "Loading packs from loading bay" ); + loadPacks(); + AppLog::info( "Done loading packs" ); + + + mainTilePicker->forceNewSearch(); + mainRoomPicker->forceNewSearch(); + mainSpritePicker->forceNewSearch(); + mainSpritePickerLower->forceNewSearch(); + mainStateObjectPicker->forceNewSearch(); + mainTimbrePicker->forceNewSearch(); + mainScalePicker->forceNewSearch(); + mainMusicPicker->forceNewSearch(); + mainSongPicker->forceNewSearch(); + mainScenePicker->forceNewSearch(); + mainPalettePicker->forceNewSearch(); + + mainDragAndDrop = new DragAndDropManager( 48, 48, 320, 240 ); + + + mainTileEditor = new TileEditor( inScreen ); + + mainRoomEditor = new RoomEditor( inScreen ); + + mainSpriteEditor = new SpriteEditor( inScreen ); + + mainStateObjectEditor = new StateObjectEditor( inScreen ); + + mainMusicEditor = new MusicEditor( inScreen ); + mainTimbreEditor = new TimbreEditor( inScreen ); + mainScaleEditor = new ScaleEditor( inScreen ); + mainSongEditor = new SongEditor( inScreen ); + mainPaletteEditor = new PaletteEditor( inScreen ); + + mGameStateEditor = new GameStateEditor( inScreen ); + + mainPracticePlayerEditor = new PlayerMoveEditor( inScreen ); + + + + + + + //mCurrentEditor = mRoomEditor; + //mCurrentEditor = mTileEditor; + //mCurrentEditor = mColorEditor; + //mCurrentEditor = mSpriteEditor; + mCurrentEditor = mGameStateEditor; + + + mCurrentEditor->setVisible( true ); + mCurrentEditor->addActionListener( this ); + + mainPracticePlayerEditor->addActionListener( this ); + mainPracticePlayerEditor->setMovesDisabled( true ); + + + practiceMode = false; + practicePlayerTurn = false; + } + + + +ControllerGame::~ControllerGame() { + mCurrentEditor->setVisible( false ); + + delete mGameStateEditor; + + delete mainColorEditor; + delete mainTileEditor; + delete mainRoomEditor; + delete mainSpriteEditor; + delete mainStateObjectEditor; + + delete mainSongEditor; + // re-NULL, in case a Player game started later + mainSongEditor = NULL; + + delete mainMusicEditor; + delete mainTimbreEditor; + delete mainScaleEditor; + delete mainPaletteEditor; + + delete mainPracticePlayerEditor; + + delete mainDragAndDrop; + + + delete mainColorStack; + delete mainTilePicker; + delete mainRoomPicker; + + delete mainSpritePicker; + delete mainSpritePickerLower; + + delete mainStateObjectPicker; + + delete mainSongPicker; + + delete mainTimbrePicker; + + delete mainScalePicker; + + delete mainPalettePicker; + + delete mainMusicPicker; + + delete mainScenePicker; + + } + + + +void ControllerGame::resetTimer() { + mainTimerDisplay->setTime( mSecondsPerMove ); + + mStepsLeft = mStepsPerSecond * mSecondsPerMove; + mLastStepTime = Time::getCurrentTime(); + } + + + +void ControllerGame::step() { + + if( ( connection != NULL && connection->isConnected() ) + || practiceMode ) { + // display and update timer + + decrementStepCount(); + + + if( mStepsLeft == mStepsPerSecond * 6 - 1) { + mainWarningDisplay->show( "warning_sending_soon", mStepsLeft ); + } + + + mainTimerDisplay->freeze( false ); + mainTimerDisplay->setTime( mStepsLeft / mStepsPerSecond ); + + if( mStepsLeft > 0 ) { + mGameStateEditor->enableSend( true ); + } + } + else { + // switch tip on timer + mainTimerDisplay->freeze( true ); + + // no sending + mGameStateEditor->enableSend( false ); + } + + + if( mStepsLeft == 0 ) { + // force a step here, regardless of system time + // we NEVER want to run step 0 more than once (send same + // state more than once) + mStepsLeft --; + + + if( connection != NULL && ! practiceMode ) { + + AppLog::info( "Sending game state" ); + + + + mGameStateEditor->aboutToSend(); + + // pack last-saved music ID into this game state + // (to share this saved music with the player's database) + mGameStateEditor->mGameStateToEdit->mMusicID = + mainMusicPicker->getSelectedResourceID(); + + // same for last-selected scene ID + mGameStateEditor->mGameStateToEdit->mSceneID = + mainScenePicker->getSelectedResourceID(); + + + + int numBytes; + unsigned char *message = + mGameStateEditor->mGameStateToEdit->getStateAsMessage( + &numBytes ); + + // stale now, because sent to player, unless we edit it + mGameStateEditor->mGameStateToEdit->markNonPlayerSpeechStale(); + + + + connection->sendMessage( message, numBytes ); + + delete [] message; + } + else if( practiceMode && + ! mainPracticePlayerEditor->isVisible() ) { + + mGameStateEditor->aboutToSend(); + + mGameStateEditor->mGameStateToEdit->markNonPlayerSpeechStale(); + + GameState *copyState = mGameStateEditor->mGameStateToEdit->copy(); + copyState->setSelectedObject( 0 ); + + mainPracticePlayerEditor->setGameStateToEdit( copyState ); + + + mainPracticePlayerEditor->mGameStateToEdit + ->markNonPlayerSpeechStale(); + + practicePlayerTurn = true; + // do this *after* taking flip book frame + //mainPracticePlayerEditor->setMovesDisabled( false ); + + // "player" (in practice mode) gets to move again + mStepsLeft = mStepsPerSecond * mSecondsPerMove; + mLastStepTime = Time::getCurrentTime(); + + // clear waiting tip + ToolTipManager::freeze( false ); + ToolTipManager::setTip( NULL ); + + mainPracticePlayerEditor->enableSend( true ); + + mGameStateEditor->showPracticePlayerEditor(); + } + else if( practiceMode && + mainPracticePlayerEditor->isVisible() ) { + + // "player" (in practice mode) is sending + mainPracticePlayerEditor->clearNonPlayerSpeech(); + + mainPracticePlayerEditor->setMovesDisabled( true ); + + // preserve selected object + int oldSelected = + mGameStateEditor->mGameStateToEdit->getSelectedObject(); + + GameState *copyState = + mainPracticePlayerEditor->mGameStateToEdit->copy(); + copyState->setSelectedObject( oldSelected ); + + mGameStateEditor->setGameStateToEdit( copyState ); + + + practicePlayerTurn = false; + // do this *after* taking flip book frame + //mGameStateEditor->hidePracticePlayerEditor(); + + mGameStateEditor->enableSend( true ); + + mGameStateEditor->mGameStateToEdit->deleteAllNonPlayerSpeech(); + + // controller gets to move again + mStepsLeft = mStepsPerSecond * mSecondsPerMove; + mLastStepTime = Time::getCurrentTime(); + + if( practiceStop ) { + // end of practice mode + practiceStop = false; + practiceMode = false; + + mainTimerDisplay->setTime( mStepsLeft / mStepsPerSecond ); + + // switch tip on timer + mainTimerDisplay->freeze( true ); + + // no sending + mGameStateEditor->enableSend( false ); + mGameStateEditor->hidePracticePlayerEditor(); + } + else { + // simulate player move received + mainWarningDisplay->show( "warning_move_received", + mStepsPerSecond * 3 ); + } + } + } + else if( mStepsLeft < 0 ) { + // try to receive response + + if( connection != NULL ) { + + int numBytes; + unsigned char *message = connection->receiveMessage( &numBytes ); + + if( message != NULL ) { + AppLog::info( "Got message" ); + + GameState *state = new GameState( message, numBytes ); + + delete [] message; + + + // only pay attention to speech, action/pos, and position for + // obj0 as sent by player + StateObjectInstance *playerObjZero = + *( state->mObjects.getElement( 0 ) ); + + GameState *ourState = mGameStateEditor->mGameStateToEdit; + + // first, clear all other speech (it's stale after player sees + // it one time) + ourState->deleteAllNonPlayerSpeech(); + + + StateObjectInstance *ourObjZero = + *( ourState->mObjects.getElement( 0 ) ); + + ourObjZero->mAnchorPosition = playerObjZero->mAnchorPosition; + + delete [] ourObjZero->mSpokenMessage; + ourObjZero->mSpokenMessage = + stringDuplicate( playerObjZero->mSpokenMessage ); + + ourObjZero->mActionOffset = playerObjZero->mActionOffset; + + memcpy( ourObjZero->mAction, playerObjZero->mAction, 11 ); + + delete state; + + + + // don't update pickers, since controller might be in the + // middle of editing something else + mGameStateEditor->setGameStateToEdit( ourState->copy(), + false ); + mGameStateEditor->enableSend( true ); + + + // we get to move again + mStepsLeft = mStepsPerSecond * mSecondsPerMove; + mLastStepTime = Time::getCurrentTime(); + + mainWarningDisplay->show( "warning_move_received", + mStepsPerSecond * 3 ); + } + } + } + + + if( connection != NULL ) { + + // check for any resource requests (channel 1) + int numBytes; + unsigned char *message = connection->receiveMessage( &numBytes, 1 ); + if( message != NULL ) { + // got one! + unsigned long sec, ms; + Time::getCurrentTime( &sec, &ms ); + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "Got a resource request (%d:%d)...", (int)sec, (int)ms ); + + int bytesLeft = numBytes; + unsigned char *messageLeft = message; + + if( bytesLeft < 1 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: bad resource request received (l:%d)", + __LINE__ ); + delete [] message; + return; + } + + int numChunks = messageLeft[0]; + + bytesLeft -= 1; + messageLeft = &( messageLeft[1] ); + + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + " Request contains %d chunks, %d bytes", numChunks, + numBytes ); + + SimpleVector responseAccum; + + + for( int i=0; ilogPrintf( + Log::ERROR_LEVEL, + "Error: bad resource request received (l:%d)", + __LINE__ ); + delete [] message; + return; + } + + int typeLength = messageLeft[0]; + + bytesLeft -= 1; + messageLeft = &( messageLeft[1] ); + + + if( bytesLeft < typeLength ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: bad resource request received (l:%d)," + " error on chunk %d", + __LINE__, i ); + delete [] message; + return; + } + + char *typeString = new char[ typeLength + 1]; + memcpy( typeString, messageLeft, typeLength ); + typeString[ typeLength ] = '\0'; + + bytesLeft -= typeLength; + messageLeft = &( messageLeft[ typeLength ] ); + + + int numUsed; + + uniqueID id = readUniqueID( messageLeft, bytesLeft, + &numUsed ); + if( numUsed != U ) { + AppLog::error( "Error: bad resource request received" ); + delete [] message; + delete [] typeString; + return; + } + + bytesLeft -= numUsed; + messageLeft = &( messageLeft[ numUsed ] ); + + + int dataLength; + char fromNetwork; + unsigned char *data = loadResourceData( typeString, + id, + &dataLength, + &fromNetwork ); + if( data == NULL ) { + char *idString = getHumanReadableString( id ); + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: request for resource %s of type %s " + "that failed to load.", idString, typeString ); + delete [] idString; + + uniqueID defaultResourceID; + + char foundDefault = true; + + if( strcmp( typeString, "music" ) == 0 ) { + defaultResourceID = + Music::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "scale" ) == 0 ) { + defaultResourceID = + Scale::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "timbre" ) == 0 ) { + defaultResourceID = + TimbreResource::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "song" ) == 0 ) { + defaultResourceID = + Song::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "object" ) == 0 ) { + defaultResourceID = + StateObject::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "room" ) == 0 ) { + defaultResourceID = + Room::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "scene" ) == 0 ) { + defaultResourceID = + Scene::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "sprite" ) == 0 ) { + defaultResourceID = + SpriteResource::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "tile" ) == 0 ) { + defaultResourceID = + Tile::getDefaultResource().getUniqueID(); + } + else if( strcmp( typeString, "palette" ) == 0 ) { + defaultResourceID = + Palette::getDefaultResource().getUniqueID(); + } + else { + foundDefault = false; + AppLog::error( + "Error, unknown resource type, cannot send " + "default" ); + } + + if( foundDefault ) { + AppLog::info( + "Sending default resource data instead" ); + data = loadResourceData( typeString, + defaultResourceID, + &dataLength, + &fromNetwork ); + } + } + + + if( data != NULL ) { + + // chunk length + responseAccum.push_back( getChars( dataLength ), 4 ); + + // chunk data + responseAccum.push_back( data, dataLength ); + + delete [] data; + } + else { + AppLog::criticalError( + "CRITICAL ERROR: failed to load any resource " + "data in response to client request" ); + } + + + + delete [] typeString; + } + + delete [] message; + + + unsigned char *responseMessage = responseAccum.getElementArray(); + + // send the data back directly + connection->sendMessage( responseMessage, responseAccum.size(), + 1 ); + + delete [] responseMessage; + + + // step network to force send through + char workLeft = true; + while( workLeft ) { + workLeft = connection->step(); + } + } + } + + + } + + + + +void ControllerGame::drawScene() { + + } + + + +void ControllerGame::postRedraw() { + if( practiceMode ) { + if( mStepsLeft == mStepsPerSecond * mSecondsPerMove ) { + + mainPracticePlayerEditor->saveFlipBookImage(); + + if( practicePlayerTurn ) { + // just received a move, and took a snapshot of it... + // now enable player to move + mainPracticePlayerEditor->setMovesDisabled( false ); + } + else { + // end of practice player turn + mGameStateEditor->hidePracticePlayerEditor(); + } + } + } + } + + + + +void ControllerGame::actionPerformed( GUIComponent *inTarget ) { + // close button + if( inTarget == mCurrentEditor && mCurrentEditor != mGameStateEditor ) { + mCurrentEditor->setVisible( false ); + mCurrentEditor->removeActionListener( this ); + mCurrentEditor = NULL; + } + else if( inTarget == mGameStateEditor || + inTarget == mainPracticePlayerEditor) { + + // event from this means "send" + // from either StateEditor or practice player + + if( inTarget == mGameStateEditor ) { + AppLog::info( "Send button pressed in GameStateEditor" ); + } + else { + AppLog::info( + "Send (or stop practice) button pressed in " + "PracticePlayerEditor" ); + } + + + if( mStepsLeft > 0 ) { + // will send on very next step + mStepsLeft = 1; + mainTimerDisplay->setTime( 0 ); + } + else { + AppLog::error( + "ERROR: Send button pressed when mStepsLeft not positive" ); + } + + + + } + + } + diff --git a/gameSource/ControllerGame.h b/gameSource/ControllerGame.h new file mode 100644 index 0000000..b35684e --- /dev/null +++ b/gameSource/ControllerGame.h @@ -0,0 +1,40 @@ +#include "GameHalf.h" +#include "Editor.h" +#include "ColorEditor.h" +#include "TileEditor.h" +#include "RoomEditor.h" +#include "SpriteEditor.h" +#include "GameStateEditor.h" +#include "PlayerMoveEditor.h" + +#include "minorGems/ui/event/ActionListenerList.h" + + +class ControllerGame : public GameHalf, public ActionListener { + public: + + ControllerGame( ScreenGL *inScreen ); + + ~ControllerGame(); + + virtual void resetTimer(); + + virtual void step(); + virtual void drawScene(); + + // pay attention to redraws + virtual void postRedraw(); + + + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + protected: + Editor *mCurrentEditor; + TileEditor *mTileEditor; + RoomEditor *mRoomEditor; + SpriteEditor *mSpriteEditor; + GameStateEditor *mGameStateEditor; + + }; diff --git a/gameSource/DemoCodeChecker.cpp b/gameSource/DemoCodeChecker.cpp new file mode 100644 index 0000000..6af1721 --- /dev/null +++ b/gameSource/DemoCodeChecker.cpp @@ -0,0 +1,181 @@ +#include "DemoCodeChecker.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/TranslationManager.h" +#include "minorGems/crypto/hashes/sha1.h" +#include "minorGems/util/log/AppLog.h" + +#include + + +// secret shared with demo server (so that we can detect that it's a real +// demo server) +static const char *sharedSecret = "control_this"; + +static const char *demoServerAddress = "http://sleepisdeath.net/demoServer/server.php"; + + + +DemoCodeChecker::DemoCodeChecker( char *inCode ) + : mError( false ), + mErrorString( NULL ) { + + char *challengeString = autoSprintf( "%d%s", time( NULL ), sharedSecret ); + + mChallenge = computeSHA1Digest( challengeString ); + + delete [] challengeString; + + + char *url = autoSprintf( "%s?action=check_permitted&demo_id=%s" + "&challenge=%s", + demoServerAddress, inCode, mChallenge ); + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, "Checking demo code with URL %s", url ); + + mRequest = new WebRequest( "GET", url, NULL ); + delete [] url; + + mPermitted = false; + + mStepCount = 0; + } + + + +DemoCodeChecker::~DemoCodeChecker() { + if( mErrorString != NULL ) { + delete [] mErrorString; + } + + if( mRequest != NULL ) { + delete mRequest; + } + + delete [] mChallenge; + } + + + +char DemoCodeChecker::step() { + + int result = mRequest->step(); + + mStepCount ++; + + if( mStepCount < 45 ) { + // wait a bit to avoid instant, flicker-inducing response + return true; + } + + if( mPermitted || isError() ) { + // don't do any more processing + return false; + } + + + + + if( result == -1 ) { + setError( "err_webRequest" ); + + // done + return false; + } + if( result == 1 ) { + // check + + char *resultString = mRequest->getResult(); + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Result from demo code server:\n%s", resultString ); + + + if( strstr( resultString, "permitted" ) != NULL ) { + + char *correctResponseString = autoSprintf( "%s%s", mChallenge, + sharedSecret ); + + char *correctResponse = computeSHA1Digest( correctResponseString ); + + delete [] correctResponseString; + + + char containsResponse = + ( strstr( resultString, correctResponse ) != NULL ); + + delete[] correctResponse; + + + + if( containsResponse ) { + mPermitted = true; + + delete [] resultString; + + return false; + } + } + + delete [] resultString; + + + // else permission failed + setError( "err_codeFailed" ); + return false; + } + if( result == 0 ) { + // request in progress + return true; + } + + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Unexpecte result code from WebRequest: %d", result ); + + setError( "err_webRequest" ); + return false; + } + + + +char DemoCodeChecker::codePermitted() { + return mPermitted; + } + + + +char DemoCodeChecker::isError() { + return mError; + } + + + +void DemoCodeChecker::clearError() { + if( mErrorString != NULL ) { + delete [] mErrorString; + } + mErrorString = NULL; + mError = false; + } + + + +char *DemoCodeChecker::getErrorString() { + return mErrorString; + } + + + +void DemoCodeChecker::setError( const char *inErrorTranslationKey ) { + mError = true; + + if( mErrorString != NULL ) { + delete [] mErrorString; + } + + mErrorString = stringDuplicate( TranslationManager::translate( + (char*)inErrorTranslationKey ) ); + } diff --git a/gameSource/DemoCodeChecker.h b/gameSource/DemoCodeChecker.h new file mode 100644 index 0000000..487a207 --- /dev/null +++ b/gameSource/DemoCodeChecker.h @@ -0,0 +1,44 @@ +#include "minorGems/network/web/WebRequest.h" + + + +class DemoCodeChecker { + public: + + // start a checker connection + DemoCodeChecker( char *inCode ); + + ~DemoCodeChecker(); + + + // perform another step of any pending network operations + // returns true if work remains to be done + // returns false if client is done with all pending work + char step(); + + // after steps done, check result + char codePermitted(); + + + char isError(); + + void clearError(); + + // destroyed internally + char *getErrorString(); + + protected: + char mError; + char *mErrorString; + + void setError( const char *inErrorTranslationKey ); + + + WebRequest *mRequest; + + char *mChallenge; + char mPermitted; + + + int mStepCount; + }; diff --git a/gameSource/DragAndDropManager.cpp b/gameSource/DragAndDropManager.cpp new file mode 100644 index 0000000..411cbbb --- /dev/null +++ b/gameSource/DragAndDropManager.cpp @@ -0,0 +1,157 @@ +#include "DragAndDropManager.h" +#include "ToolTipManager.h" + + + +DragAndDropManager::DragAndDropManager( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : GUIComponentGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mNumSprites( 0 ), + mSprites( NULL ), + mOffsets( NULL ), + mTrans( NULL ), + mGlows( NULL ), + mDragging( false ) { + + } + + + +DragAndDropManager::~DragAndDropManager() { + clear(); + } + + + +void DragAndDropManager::clear() { + if( mSprites != NULL ) { + for( int i=0; idraw( 0, 0, &pos, mZoom, mTrans[i] ); + + + if( mGlows[i] ) { + // back to normal blend + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + } + } + + } + diff --git a/gameSource/DragAndDropManager.h b/gameSource/DragAndDropManager.h new file mode 100644 index 0000000..3a971cc --- /dev/null +++ b/gameSource/DragAndDropManager.h @@ -0,0 +1,68 @@ +#ifndef DRAG_AND_DROP_MANAGER_INCLUDED +#define DRAG_AND_DROP_MANAGER_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" + +#include "Sprite.h" +#include "common.h" + + + +// transparent and covers whole screen. +// should be added last in root panel so that it is on top +class DragAndDropManager : public GUIComponentGL { + + public: + DragAndDropManager( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual ~DragAndDropManager(); + + char isDragging(); + + + // sets one sprite with 0 offset + // destroyed internally. + // set to NULL to clear + virtual void setSprite( Sprite *inSprite, double inZoom ); + + // set of sprites with specific offsets + // arrays and sprites destroyed internally + // cleared by passing null to above function + virtual void setSprites( int inNumSprites, + Sprite **inSprites, intPair *inOffsets, + float *inTrans, + char *inGlow, + double inZoom ); + + + // override + virtual void mousePressed( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + virtual void fireRedraw(); + + protected: + void clear(); + + + int mNumSprites; + Sprite **mSprites; + intPair *mOffsets; + float *mTrans; + char *mGlows; + + double mZoom; + + double mX; + double mY; + + char mDragging; + + + }; + + + +#endif diff --git a/gameSource/DrawToolSet.cpp b/gameSource/DrawToolSet.cpp new file mode 100644 index 0000000..a012af2 --- /dev/null +++ b/gameSource/DrawToolSet.cpp @@ -0,0 +1,150 @@ +#include "DrawToolSet.h" + + + +DrawToolSet::DrawToolSet( double inAnchorX, double inAnchorY, + const char *inPickButtonTipKey, + char inShowStamp ) + : GUIPanelGL( inAnchorX, inAnchorY, 120, 20, + // transparent bg + new Color( 0, 0, 0, 0 ) ) { + + mPenButton = new SelectableButtonGL( + new Sprite( "pen.tga", true ), + 1, + inAnchorX, inAnchorY, 20, 20 ); + + mHorLineButton = new SelectableButtonGL( + new Sprite( "horLine.tga", true ), + 1, + inAnchorX + 20, inAnchorY, 20, 20 ); + + mVerLineButton = new SelectableButtonGL( + new Sprite( "verLine.tga", true ), + 1, + inAnchorX + 40, inAnchorY, 20, 20 ); + + mFillButton = new SelectableButtonGL( + new Sprite( "fill.tga", true ), + 1, + inAnchorX + 60, inAnchorY, 20, 20 ); + + mPickColorButton = new SelectableButtonGL( + new Sprite( "pickColor.tga", true ), + 1, + inAnchorX + 80, inAnchorY, 20, 20 ); + + mStampButton = new SelectableButtonGL( + new Sprite( "stamp.tga", true ), + 1, + inAnchorX + 100, inAnchorY, 20, 20 ); + + + mPenButton->setToolTip( "tip_pen" ); + mHorLineButton->setToolTip( "tip_horLine" ); + mVerLineButton->setToolTip( "tip_verLine" ); + mFillButton->setToolTip( "tip_fill" ); + mPickColorButton->setToolTip( inPickButtonTipKey ); + mStampButton->setToolTip( "tip_stamp" ); + + + + add( mPenButton ); + add( mHorLineButton ); + add( mVerLineButton ); + add( mFillButton ); + add( mPickColorButton ); + add( mStampButton ); + + mPenButton->addActionListener( this ); + mHorLineButton->addActionListener( this ); + mVerLineButton->addActionListener( this ); + mFillButton->addActionListener( this ); + mPickColorButton->addActionListener( this ); + mStampButton->addActionListener( this ); + + mStampButton->setEnabled( inShowStamp ); + + mPenButton->setSelected( true ); + + mLastSelectedBeforeShift = mPenButton; + } + + + +drawTool DrawToolSet::getSelected() { + if( mPenButton->getSelected() ) { + return pen; + } + if( mHorLineButton->getSelected() ) { + return horLine; + } + if( mVerLineButton->getSelected() ) { + return verLine; + } + if( mFillButton->getSelected() ) { + return fill; + } + if( mPickColorButton->getSelected() ) { + return pickColor; + } + if( mStampButton->getSelected() ) { + return stamp; + } + + return pen; + } + + + +void DrawToolSet::actionPerformed( GUIComponent *inTarget ) { + // select hit, turn others off + mPenButton->setSelected( inTarget == mPenButton ); + mHorLineButton->setSelected( inTarget == mHorLineButton ); + mVerLineButton->setSelected( inTarget == mVerLineButton ); + mFillButton->setSelected( inTarget == mFillButton ); + mPickColorButton->setSelected( inTarget == mPickColorButton ); + mStampButton->setSelected( inTarget == mStampButton ); + + fireActionPerformed( this ); + } + + + +char DrawToolSet::isFocused() { + return true; + } + +void DrawToolSet::specialKeyPressed( int inKey, double inX, double inY ) { + if( inKey == MG_KEY_RSHIFT || inKey == MG_KEY_LSHIFT ) { + // save last selected + if( mPenButton->getSelected() ) { + mLastSelectedBeforeShift = mPenButton; + } + else if( mHorLineButton->getSelected() ) { + mLastSelectedBeforeShift = mHorLineButton; + } + else if( mVerLineButton->getSelected() ) { + mLastSelectedBeforeShift = mVerLineButton; + } + else if( mFillButton->getSelected() ) { + mLastSelectedBeforeShift = mFillButton; + } + else if( mPickColorButton->getSelected() ) { + mLastSelectedBeforeShift = mPickColorButton; + } + else if( mStampButton->getSelected() ) { + mLastSelectedBeforeShift = mStampButton; + } + + actionPerformed( mPickColorButton ); + } + } + +void DrawToolSet::specialKeyReleased( int inKey, double inX, double inY ) { + if( inKey == MG_KEY_RSHIFT || inKey == MG_KEY_LSHIFT ) { + actionPerformed( mLastSelectedBeforeShift ); + } + } + + diff --git a/gameSource/DrawToolSet.h b/gameSource/DrawToolSet.h new file mode 100644 index 0000000..c5a1d67 --- /dev/null +++ b/gameSource/DrawToolSet.h @@ -0,0 +1,54 @@ +#ifndef DRAW_TOOL_SET_INCLUDED +#define DRAW_TOOL_SET_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/ui/event/ActionListener.h" +#include "minorGems/ui/event/ActionListenerList.h" + +#include "buttons.h" + + +enum drawTool { pen, horLine, verLine, fill, pickColor, stamp }; + + +class DrawToolSet : public GUIPanelGL, public ActionListener, + public ActionListenerList { + + + public: + + // sets its width/height automatically + // also can specify translation key for pick button tool tip + DrawToolSet( double inAnchorX, double inAnchorY, + const char *inPickButtonTipKey = "tip_pickColor", + char inShowStamp = true ); + + + drawTool getSelected(); + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + // always focused, listens for shift to switch to picker tool + virtual char isFocused(); + virtual void specialKeyPressed( int inKey, double inX, double inY ); + virtual void specialKeyReleased( int inKey, double inX, double inY ); + + protected: + + SelectableButtonGL *mPenButton; + SelectableButtonGL *mHorLineButton; + SelectableButtonGL *mVerLineButton; + SelectableButtonGL *mFillButton; + SelectableButtonGL *mPickColorButton; + SelectableButtonGL *mStampButton; + + + // for when shift is released + SelectableButtonGL *mLastSelectedBeforeShift; + + }; + + +#endif diff --git a/gameSource/Editor.cpp b/gameSource/Editor.cpp new file mode 100644 index 0000000..e32c91d --- /dev/null +++ b/gameSource/Editor.cpp @@ -0,0 +1,549 @@ +#include "Editor.h" +#include "BorderPanel.h" +#include "buttons.h" +#include "ColorEditor.h" +#include "TileEditor.h" +#include "RoomEditor.h" +#include "SpriteEditor.h" +#include "StateObjectEditor.h" +#include "SongEditor.h" +#include "MusicEditor.h" +#include "TimbreEditor.h" +#include "ScaleEditor.h" +#include "PaletteEditor.h" +#include "PlayerMoveEditor.h" + +#include "SpritePicker.h" + +#include "ToolTipDisplay.h" +#include "TimerDisplay.h" +#include "WarningDisplay.h" + + +extern int gameWidth, gameHeight; + +extern ColorEditor *mainColorEditor; +extern TileEditor *mainTileEditor; +extern RoomEditor *mainRoomEditor; +extern SpriteEditor *mainSpriteEditor; +extern StateObjectEditor *mainStateObjectEditor; +extern MusicEditor *mainMusicEditor; +extern TimbreEditor *mainTimbreEditor; +extern ScaleEditor *mainScaleEditor; +extern SongEditor *mainSongEditor; +extern PaletteEditor *mainPaletteEditor; +extern PlayerMoveEditor *mainPracticePlayerEditor; + + +extern SpritePicker *mainSpritePicker; +extern SpritePicker *mainSpritePickerLower; + + +extern ToolTipDisplay *mainTipDisplay; +extern TimerDisplay *mainTimerDisplay; +extern WarningDisplay *mainWarningDisplay; + + + +Editor::Editor( ScreenGL *inScreen, char inHasMainArea, char inHasSidePanel ) + : mScreen( inScreen ), + mVisible( false ), + mHasSidePanel( inHasSidePanel ), + mHasMainArea( inHasMainArea ) { + + + double mainPanelXOffset = 0; + + if( !mHasMainArea ) { + mainPanelXOffset = gameHeight; + } + + + // panel covering whole screen + mMainPanel = new PressActionGUIPanelGL( mainPanelXOffset, 0, + gameWidth - mainPanelXOffset, + gameWidth, + new Color( 0, 0, 0, 1.0 ) ); + + + + mMainPanelGuiTranslator = new GUITranslatorGL( mMainPanel, mScreen, + gameWidth ); + + mSidePanel = NULL; + + if( mHasSidePanel ) { + + // panel covering right edge of screen, + // leaving a perfect square to left + mSidePanel = new BorderPanel( gameHeight, 40, + gameWidth - gameHeight, gameHeight, + new Color( 0, 0, 0, 1.0 ), + new Color( .4, .4, .4, 1.0 ), 2 ); + + + + + } + + mCloseButton = NULL; + + if( ! mHasMainArea && mHasSidePanel ) { + + // don't attach key equivalent for button that won't close whole + // window + mCloseButton = new NoKeyCloseButtonGL( 242, 270, 8, 8 ); + mSidePanel->add( mCloseButton ); + } + else { + // whole window close... key equivalent + mCloseButton = new CloseButtonGL( 0, 272, 8, 8 ); + // add to Main on top of all else + } + + if( mCloseButton != NULL ) { + mCloseButton->addActionListener( this ); + } + + + } + + + +void Editor::postConstruction() { + // put side panel and main widgets on top of everything else + + postConstructionSide(); + postConstructionMain(); + } + + + +void Editor::postConstructionSide() { + + + if( mHasSidePanel ) { + // on top of everything else + mSidePanel->add( mainWarningDisplay ); + + + mMainPanel->add( mSidePanel ); + } + } + + + +void Editor::postConstructionMain() { + if( ! mHasMainArea && mHasSidePanel ) { + // side-panel only, nothing left to add + } + else { + mMainPanel->add( mCloseButton ); + + mMainPanel->add( mainTipDisplay ); + mMainPanel->add( mainTimerDisplay ); + } + } + + +Editor::~Editor() { + + setVisible( false ); + + if( mSidePanel != NULL ) { + mSidePanel->remove( mainWarningDisplay ); + + if( ! mMainPanel->contains( mSidePanel ) ) { + delete mSidePanel; + } + } + + + if( mMainPanel->contains( mainTipDisplay ) ) { + // we don't destroy this + mMainPanel->remove( mainTipDisplay ); + mMainPanel->remove( mainTimerDisplay ); + } + + + + // recursively deletes all sub-components + delete mMainPanelGuiTranslator; + } + + + + +void Editor::setVisible( char inIsVisible ) { + + if( inIsVisible && !mVisible) { + + mScreen->addSceneHandler( mMainPanelGuiTranslator ); + mScreen->addKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->addMouseHandler( mMainPanelGuiTranslator ); + } + if( !inIsVisible && mVisible) { + mScreen->removeSceneHandler( mMainPanelGuiTranslator ); + mScreen->removeKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->removeMouseHandler( mMainPanelGuiTranslator ); + } + + mVisible = inIsVisible; + } + + + +char Editor::isVisible() { + return mVisible; + } + + + +void Editor::showColorEditor() { + mainColorEditor->addActionListener( this ); + + if( mSidePanel != NULL ) { + mMainPanel->remove( mSidePanel ); + } + + mainColorEditor->setVisible( true ); + } + + + + +void Editor::showTileEditor() { + setVisible( false ); + + mainTileEditor->addActionListener( this ); + + mainTileEditor->setVisible( true ); + } + + + +void Editor::showRoomEditor() { + setVisible( false ); + + mainRoomEditor->addActionListener( this ); + + mainRoomEditor->setVisible( true ); + } + + + +void Editor::showSpriteEditor() { + setVisible( false ); + + // make sure pickers match (editor shows mainSpritePicker) + mainSpritePickerLower->cloneState( mainSpritePicker ); + + + mainSpriteEditor->addActionListener( this ); + + mainSpriteEditor->setVisible( true ); + } + + + +void Editor::showStateObjectEditor() { + setVisible( false ); + + mainStateObjectEditor->addActionListener( this ); + + mainStateObjectEditor->setVisible( true ); + } + + + +void Editor::showMusicEditor() { + setVisible( false ); + + mainMusicEditor->addActionListener( this ); + + mainMusicEditor->setVisible( true ); + } + +void Editor::showTimbreEditor() { + setVisible( false ); + + mainTimbreEditor->addActionListener( this ); + + mainTimbreEditor->setVisible( true ); + } + +void Editor::showScaleEditor() { + setVisible( false ); + + mainScaleEditor->addActionListener( this ); + + mainScaleEditor->setVisible( true ); + } + + +void Editor::showSongEditor() { + setVisible( false ); + + mainSongEditor->addActionListener( this ); + + mainSongEditor->setVisible( true ); + } + + + +void Editor::showPaletteEditor() { + setVisible( false ); + + mainPaletteEditor->addActionListener( this ); + + mainPaletteEditor->setVisible( true ); + } + + + +void Editor::showPracticePlayerEditor() { + setVisible( false ); + + // any sub-editor open? + + if( mainPaletteEditor->isVisible() ) { + if( mainColorEditor->isVisible() ) { + mainColorEditor->setVisible( false ); + mainColorEditor->removeActionListener( mainPaletteEditor ); + + if( mainPaletteEditor->mSidePanel != NULL ) { + mainPaletteEditor->mMainPanel->add( + mainPaletteEditor->mSidePanel ); + } + } + mainPaletteEditor->setVisible( false ); + + if( mainPaletteEditor->isListening( mainTileEditor ) ) { + + mainPaletteEditor->removeActionListener( mainTileEditor ); + mainTileEditor->setVisible( true ); + } + else if( mainPaletteEditor->isListening( mainRoomEditor ) ) { + + mainPaletteEditor->removeActionListener( mainRoomEditor ); + mainRoomEditor->setVisible( true ); + } + } + + + if( mainTileEditor->isVisible() ) { + + if( mainColorEditor->isVisible() ) { + mainColorEditor->setVisible( false ); + mainColorEditor->removeActionListener( mainTileEditor ); + + if( mainTileEditor->mSidePanel != NULL ) { + mainTileEditor->mMainPanel->add( mainTileEditor->mSidePanel ); + } + } + mainTileEditor->setVisible( false ); + mainTileEditor->removeActionListener( mainRoomEditor ); + mainRoomEditor->setVisible( true ); + } + + + if( mainSpriteEditor->isVisible() ) { + + if( mainColorEditor->isVisible() ) { + mainColorEditor->setVisible( false ); + mainColorEditor->removeActionListener( mainSpriteEditor ); + + if( mainSpriteEditor->mSidePanel != NULL ) { + mainSpriteEditor->mMainPanel->add( + mainSpriteEditor->mSidePanel ); + } + } + mainSpriteEditor->setVisible( false ); + mainSpriteEditor->removeActionListener( mainStateObjectEditor ); + mainStateObjectEditor->setVisible( true ); + } + + if( mainTimbreEditor->isVisible() ) { + + mainTimbreEditor->setVisible( false ); + mainTimbreEditor->removeActionListener( mainSongEditor ); + mainSongEditor->setVisible( true ); + } + + if( mainScaleEditor->isVisible() ) { + + mainScaleEditor->setVisible( false ); + mainScaleEditor->removeActionListener( mainSongEditor ); + mainSongEditor->setVisible( true ); + } + + if( mainMusicEditor->isVisible() ) { + mainMusicEditor->setVisible( false ); + mainMusicEditor->removeActionListener( mainSongEditor ); + mainSongEditor->setVisible( true ); + } + + + + if( mainRoomEditor->isVisible() ) { + // special case for RoomEditor, so it can reset shared + // GameStateDisplay + mainRoomEditor->editorClosing(); + + mainRoomEditor->setVisible( false ); + mainRoomEditor->removeActionListener( this ); + } + + + if( mainStateObjectEditor->isVisible() ) { + mainStateObjectEditor->setVisible( false ); + mainRoomEditor->removeActionListener( this ); + } + + if( mainSongEditor->isVisible() ) { + mainSongEditor->setVisible( false ); + mainSongEditor->removeActionListener( this ); + } + + + mainPracticePlayerEditor->setVisible( true ); + } + + + +void Editor::hidePracticePlayerEditor() { + mainPracticePlayerEditor->setVisible( false ); + + setVisible( true ); + } + + + +void Editor::redrawMainPanel() { + mMainPanel->fireRedraw(); + } + + + +void Editor::actionPerformed( GUIComponent *inTarget ) { + if( mCloseButton != NULL && inTarget == mCloseButton ) { + + // tell subclass about it + editorClosing(); + + + if( this != mainColorEditor && + mainColorEditor->isVisible() ) { + // color editor on top of our side panel + + // force it to close and redisplay our side panel + mainColorEditor->setVisible( false ); + mainColorEditor->removeActionListener( this ); + + if( mSidePanel != NULL ) { + mMainPanel->add( mSidePanel ); + } + } + + // fire action telling others that we got a close-button press + fireActionPerformed( this ); + } + else if( inTarget == mainColorEditor ) { + + // color editor closed + mainColorEditor->setVisible( false ); + mainColorEditor->removeActionListener( this ); + + if( mSidePanel != NULL ) { + mMainPanel->add( mSidePanel ); + } + colorEditorClosed(); + } + else if( inTarget == mainTileEditor ) { + // tile editor closed + + mainTileEditor->setVisible( false ); + mainTileEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainRoomEditor ) { + // room editor closed + + mainRoomEditor->setVisible( false ); + mainRoomEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainSpriteEditor ) { + // sprite editor closed + + mainSpriteEditor->setVisible( false ); + mainSpriteEditor->removeActionListener( this ); + + + // make sure pickers match (editor showed mainSpritePicker, so copy + // changes from there back into Lower picker) + mainSpritePicker->cloneState( mainSpritePickerLower ); + + + setVisible( true ); + } + else if( inTarget == mainStateObjectEditor ) { + // object editor closed + + mainStateObjectEditor->setVisible( false ); + mainStateObjectEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainMusicEditor ) { + // music editor closed + + mainMusicEditor->setVisible( false ); + mainMusicEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainTimbreEditor ) { + // music editor closed + + mainTimbreEditor->setVisible( false ); + mainTimbreEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainScaleEditor ) { + // music editor closed + + mainScaleEditor->setVisible( false ); + mainScaleEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainSongEditor ) { + // song editor closed + + mainSongEditor->setVisible( false ); + mainSongEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainPaletteEditor ) { + // music editor closed + + mainPaletteEditor->setVisible( false ); + mainPaletteEditor->removeActionListener( this ); + + setVisible( true ); + } + else if( inTarget == mainPracticePlayerEditor ) { + // practice player editor closed + + mainPracticePlayerEditor->setVisible( false ); + mainPracticePlayerEditor->removeActionListener( this ); + + setVisible( true ); + } + + } diff --git a/gameSource/Editor.h b/gameSource/Editor.h new file mode 100644 index 0000000..c8b0238 --- /dev/null +++ b/gameSource/Editor.h @@ -0,0 +1,126 @@ +#ifndef EDITOR_INCLUDED +#define EDITOR_INCLUDED + + +#include "buttons.h" + + +#include "minorGems/graphics/openGL/ScreenGL.h" +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/graphics/openGL/gui/GUITranslatorGL.h" + +#include "minorGems/ui/event/ActionListener.h" +#include "minorGems/ui/event/ActionListenerList.h" + + +#include "PressActionGUIPanelGL.h" +#include "BorderPanel.h" + + +class Editor : public GUIComponent, + public ActionListener, public ActionListenerList { + + public: + + + virtual ~Editor(); + + + // defaults to invisible + virtual void setVisible( char inIsVisible ); + + virtual char isVisible(); + + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + // hides our side panel and shows color editor until close + virtual void showColorEditor(); + + + // hides our panels and shows tile editor until it is closed + virtual void showTileEditor(); + + // hides our panels and shows room editor until it is closed + virtual void showRoomEditor(); + + // hides our panels and shows sprite editor until it is closed + virtual void showSpriteEditor(); + + // hides our panels and shows Object editor until it is closed + virtual void showStateObjectEditor(); + + // hides our panels and shows music editor until it is closed + virtual void showMusicEditor(); + + // hides our panels and shows timbre editor until it is closed + virtual void showTimbreEditor(); + + // hides our panels and shows scale editor until it is closed + virtual void showScaleEditor(); + + // hides our panels and shows song editor until it is closed + virtual void showSongEditor(); + + // hides our panels and shows palette editor until it is closed + virtual void showPaletteEditor(); + + virtual void showPracticePlayerEditor(); + virtual void hidePracticePlayerEditor(); + + + + // hack to draw main panel NOW... useful for making flip books + virtual void redrawMainPanel(); + + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing() = 0; + + + protected: + + Editor( ScreenGL *inScreen, char inHasMainArea = true, + char inHasSidePanel = true ); + + // must be called by sub-classes at end of their constructors + // (adds GUI components that must go on top of everything else) + void postConstruction(); + + // gives more control over overlay order + // those that don't need it call above + void postConstructionSide(); + void postConstructionMain(); + + + // can be overridden by subclasses to receive notification that + // an overlying color editor closed + virtual void colorEditorClosed() {}; + + + + ScreenGL *mScreen; + + + char mVisible; + + BorderPanel *mSidePanel; + PressActionGUIPanelGL *mMainPanel; + + + char mHasSidePanel, mHasMainArea; + + + + GUITranslatorGL *mMainPanelGuiTranslator; + + + SpriteButtonGL *mCloseButton; + + }; + + + +#endif diff --git a/gameSource/Envelope.cpp b/gameSource/Envelope.cpp new file mode 100644 index 0000000..6de2929 --- /dev/null +++ b/gameSource/Envelope.cpp @@ -0,0 +1,209 @@ +#include "Envelope.h" + +#include + +#include "minorGems/util/log/AppLog.h" + + +// #include + + +Envelope::Envelope( double inAttackTime, double inDecayTime, + double inSustainLevel, double inReleaseTime, + int inMinNoteLengthInGridSteps, + int inMaxNoteLengthInGridSteps, + int inGridStepDurationInSamples ) + : mActiveNoteCount( 0 ), + mGridStepDurationInSamples( inGridStepDurationInSamples ), + mMinEnvNum( inMinNoteLengthInGridSteps ), + mNumComputedEnvelopes( inMaxNoteLengthInGridSteps ), + mEvelopeLengths( new int[ inMaxNoteLengthInGridSteps ] ), + mComputedEnvelopes( new double*[ inMaxNoteLengthInGridSteps ] ) { + + for( int i=mMinEnvNum-1; i 0 ); + + mComputedEnvelopes[i][s] = t / inAttackTime; + } + else if( t < inAttackTime + inDecayTime ) { + // assert( inDecayTime > 0 ); + + // decay down to sustain level + mComputedEnvelopes[i][s] = + ( 1.0 - inSustainLevel ) * + ( inAttackTime + inDecayTime - t ) / + ( inDecayTime ) + + inSustainLevel; + } + else if( 1.0 - t > inReleaseTime ) { + mComputedEnvelopes[i][s] = inSustainLevel; + } + else { + if( inReleaseTime > 0 ) { + + mComputedEnvelopes[i][s] = + inSustainLevel - + inSustainLevel * + ( inReleaseTime - ( 1.0 - t ) ) / inReleaseTime; + } + else { + // release time 0 + // hold sustain until end + mComputedEnvelopes[i][s] = inSustainLevel; + } + } + + } + + // test code to output evelopes for plotting in gnuplot + if( false && i == 0 ) { + FILE *file = fopen( "env0.txt", "w" ); + for( int s=0; s 0 ); + + thisEnv[s] = t / inAttackTime; + } + //else if( t < inAttackTime + inHoldTime ) { + else if( s < attackHoldSamples ) { + // assert( inDecayTime > 0 ); + + // hold at 1 + thisEnv[s] = 1.0; + } + else { + if( inReleaseTime > 0 && + //t < inAttackTime + inHoldTime + inReleaseTime ) { + s < attackHoldReleaseSamples ) { + + thisEnv[s] = + 1.0 - + ( t - ( inAttackTime + inHoldTime ) ) + / inReleaseTime; + } + else { + // release time 0 + // end immediately after hold + thisEnv[s] = 0; + } + } + + } + */ + // test code to output evelopes for plotting in gnuplot + if( false && i == 0 ) { + FILE *file = fopen( "env0.txt", "w" ); + for( int s=0; s mNumComputedEnvelopes || + inNoteLengthInGridSteps < mMinEnvNum ) { + + AppLog::error( "Error: evelope for unsupported number of" + " grid steps requested" ); + + inNoteLengthInGridSteps = mMinEnvNum; + } + + return mComputedEnvelopes[ inNoteLengthInGridSteps - 1 ]; + } + + diff --git a/gameSource/Envelope.h b/gameSource/Envelope.h new file mode 100644 index 0000000..186cc2d --- /dev/null +++ b/gameSource/Envelope.h @@ -0,0 +1,80 @@ + + +class Envelope { + public: + + + /** + * Constructs an ADSR envelope. + * + * All parameters are in range 0..1, and sum of the three time + * parameters must be <= 1. + * + * @param inMinNoteLengthInGridSteps the minimum note length to + * cover. Exact envelopes will be generated for notes starting at + * this length. + * @param inMaxNoteLengthInGridSteps the maximum note length to + * cover. Exact envelopes will be generated for notes up + * to this length. + * @param inGridStepDurationInSamples the number of samples + * per grid step. + */ + Envelope( double inAttackTime, double inDecayTime, + double inSustainLevel, double inReleaseTime, + int inMinNoteLengthInGridSteps, + int inMaxNoteLengthInGridSteps, + int inGridStepDurationInSamples ); + + + /** + * Constructs an AHR envelope (sustain at 1 during hold) + * + * All parameters are in range 0..1, and sum of the three time + * parameters must be <= 1. + * + * @param inMinNoteLengthInGridSteps the minimum note length to + * cover. Exact envelopes will be generated for notes starting at + * this length. + * @param inMaxNoteLengthInGridSteps the maximum note length to + * cover. Exact envelopes will be generated for notes up + * to this length. + * @param inGridStepDurationInSamples the number of samples + * per grid step. + */ + Envelope( double inAttackTime, double inHoldTime, + double inReleaseTime, + int inMinNoteLengthInGridSteps, + int inMaxNoteLengthInGridSteps, + int inGridStepDurationInSamples ); + + + ~Envelope(); + + + + /** + * Gets an evenlope for a given note length. + * + * @return an evelope of values in [0,1] that can be indexed by + * sample number. Will be destroyed when this class is destroyed. + */ + double *getEnvelope( int inNoteLengthInGridSteps ); + + + + // how many playing notes are still using this envelope? + int mActiveNoteCount; + + + int mGridStepDurationInSamples; + + + private: + int mMinEnvNum; + + int mNumComputedEnvelopes; + + int *mEvelopeLengths; + + double **mComputedEnvelopes; + }; diff --git a/gameSource/FixedTipDisplay.cpp b/gameSource/FixedTipDisplay.cpp new file mode 100644 index 0000000..238b74e --- /dev/null +++ b/gameSource/FixedTipDisplay.cpp @@ -0,0 +1,37 @@ + +#include "FixedTipDisplay.h" +#include "minorGems/util/stringUtils.h" +#include + + +FixedTipDisplay::FixedTipDisplay( double inAnchorX, double inAnchorY ) + : ToolTipDisplay( inAnchorX, inAnchorY ), + mTip( NULL ) { + + } + + +FixedTipDisplay::~FixedTipDisplay() { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + } + + + +char *FixedTipDisplay::getTip() { + return mTip; + } + + +void FixedTipDisplay::setTip( char *inTip ) { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + + if( inTip != NULL ) { + mTip = stringDuplicate( inTip ); + } + } diff --git a/gameSource/FixedTipDisplay.h b/gameSource/FixedTipDisplay.h new file mode 100644 index 0000000..910d427 --- /dev/null +++ b/gameSource/FixedTipDisplay.h @@ -0,0 +1,26 @@ + +#include "ToolTipDisplay.h" + + +class FixedTipDisplay : public ToolTipDisplay { + + public: + + FixedTipDisplay( double inAnchorX, double inAnchorY ); + + virtual ~FixedTipDisplay(); + + + // can be NULL, copied internally, so destroyed by caller + void setTip( char *inTip ); + + protected: + + // override to take tip from Fixed manager + virtual char *getTip(); + + + char *mTip; + }; + + diff --git a/gameSource/GameHalf.cpp b/gameSource/GameHalf.cpp new file mode 100644 index 0000000..6072e9e --- /dev/null +++ b/gameSource/GameHalf.cpp @@ -0,0 +1,295 @@ +#include "GameHalf.h" + + +#include "resourceManager.h" +#include "resourceDatabase.h" +#include "usageDatabase.h" +#include "speechHints.h" + +#include "SpriteResource.h" +#include "Room.h" +#include "StateObject.h" +#include "TimbreResource.h" +#include "Scale.h" +#include "Music.h" +#include "Song.h" +#include "Scene.h" +#include "Palette.h" + + +#include "ToolTipManager.h" +#include "ToolTipDisplay.h" + +#include "TimerDisplay.h" +#include "WarningDisplay.h" + +#include "imageCache.h" + +#include "packSaver.h" + +#include "minorGems/system/Time.h" +#include "minorGems/util/TranslationManager.h" + + + + +ToolTipDisplay *mainTipDisplay; +TimerDisplay *mainTimerDisplay; + +WarningDisplay *mainWarningDisplay; + + +GameHalf::GameHalf() + : mQuitting( false ) { + + initImageCache(); + + // init this first so it creates dir where stringDB can go. + initResourceManager(); + initDatabase(); + + initUsageDatabase(); + + initSpeechHints(); + + /* + for( int i=0; i<5; i++ ) { + initUsageDatabase(); + freeUsageDatabase(); + } + exit( 0 ); + */ + + + + + Room::staticInit(); + + SpriteResource::staticInit(); + + ToolTipManager::staticInit(); + + StateObject::staticInit(); + + TimbreResource::staticInit(); + + Scale::staticInit(); + + Music::staticInit(); + + Song::staticInit(); + + + Scene::staticInit(); + + Palette::staticInit(); + + initPackSaver(); + + + mainTimerDisplay = new TimerDisplay( 16, 272 ); + + mainTipDisplay = new ToolTipDisplay( 48, 272 ); + + mainWarningDisplay = new WarningDisplay( 299, 150 ); + } + + +GameHalf::~GameHalf() { + delete mainTipDisplay; + delete mainTimerDisplay; + delete mainWarningDisplay; + + ToolTipManager::staticFree(); + + Room::staticFree(); + SpriteResource::staticFree(); + + StateObject::staticFree(); + + Song::staticFree(); + + Music::staticFree(); + + TimbreResource::staticFree(); + + Scale::staticFree(); + + Scene::staticFree(); + + Palette::staticFree(); + + freePackSaver(); + + freeSpeechHints(); + freeUsageDatabase(); + freeDatabase(); + freeResourceManager(); + + + freeImageCache(); + } + + + +char GameHalf::isQuitting() { + return mQuitting; + } + + + + +extern char hardToQuitMode; + +static unsigned char lastKeyPressed = '\0'; + +extern char practiceMode; + + +static void cancelOurTip() { + // is our tip still showing? + char *tip = ToolTipManager::getTip(); + + char *ourTip = (char *)TranslationManager::translate( + "quitQuestion" ); + + if( tip != NULL && + strcmp( tip, ourTip ) == 0 ) { + + // unfreeze + ToolTipManager::freeze( false ); + // clear it instantly (no fade) + ToolTipManager::setTip( "" ); + } + } + + +void GameHalf::keyPressed( unsigned char inKey, int inX, int inY ) { + //if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + if( !hardToQuitMode ) { + + if( lastKeyPressed == 27 ) { + // is our tip no longer showing? + char *tip = ToolTipManager::getTip(); + + char *ourTip = (char *)TranslationManager::translate( + "quitQuestion" ); + + if( tip == NULL || + strcmp( tip, ourTip ) != 0 ) { + + // reset last key to look for another ESC press + lastKeyPressed = '\0'; + } + } + + + // ESC + if( lastKeyPressed == 27 && ( inKey == 'y' || inKey == 'Y' ) ) { + //exit( 0 ); + mQuitting = true; + practiceMode = false; + + // block "y" event from going to other handlers and showing + // up in a speech bubble or search box + mEatEvent = true; + } + else if( lastKeyPressed == 27 && ( inKey != 'y' && inKey != 'Y' ) ) { + // cancel it + + cancelOurTip(); + } + else { + if( inKey == 27 ) { + // override freeze + ToolTipManager::freeze( false ); + + ToolTipManager::setTip( + (char *)TranslationManager::translate( + "quitQuestion" ) ); + // re-freeze until Y or another key pressed + ToolTipManager::freeze( true ); + } + } + lastKeyPressed = inKey; + } + else { + // # followed by ESC + if( lastKeyPressed == '#' && inKey == 27 ) { + //exit( 0 ); + mQuitting = true; + practiceMode = false; + } + lastKeyPressed = inKey; + } + + } + + +void GameHalf::mouseDragged( int inX, int inY ) { + if( lastKeyPressed == 27 ) { + cancelOurTip(); + } + } + + + +void GameHalf::mousePressed( int inX, int inY ) { + if( lastKeyPressed == 27 ) { + cancelOurTip(); + } + } + + +void GameHalf::mouseReleased( int inX, int inY ) { + if( lastKeyPressed == 27 ) { + cancelOurTip(); + } + } + + + + + +void GameHalf::decrementStepCount() { + if( mStepsLeft == mStepsPerSecond * mSecondsPerMove ) { + // first step, just take a single step regardless of time + mStepsLeft --; + mLastStepTime = Time::getCurrentTime(); + } + else if( Time::getCurrentTime() - mLastStepTime >= + 1.0 / mStepsPerSecond ) { + + // subsequent steps based on time + + // at least one real-time step has passed + + double numPassed = ( Time::getCurrentTime() - mLastStepTime ) + * mStepsPerSecond ; + + // round down + int wholeNumPassed = (int)numPassed; + + double extraTime = + ( numPassed - wholeNumPassed ) / mStepsPerSecond; + + + char passingZero = false; + + if( mStepsLeft > 0 ) { + passingZero = true; + } + + mStepsLeft -= wholeNumPassed; + mLastStepTime = Time::getCurrentTime(); + + // add extra for next step + mLastStepTime -= extraTime; + + + if( mStepsLeft < 0 && passingZero) { + mStepsLeft = 0; + } + } + } + diff --git a/gameSource/GameHalf.h b/gameSource/GameHalf.h new file mode 100644 index 0000000..124f373 --- /dev/null +++ b/gameSource/GameHalf.h @@ -0,0 +1,92 @@ +#ifndef GAME_HALF_INCLUDED +#define GAME_HALF_INCLUDED + + +#include "minorGems/graphics/openGL/MouseHandlerGL.h" +#include "minorGems/graphics/openGL/KeyboardHandlerGL.h" +#include "minorGems/graphics/openGL/SceneHandlerGL.h" +#include "minorGems/graphics/openGL/RedrawListenerGL.h" +#include "minorGems/graphics/openGL/ScreenGL.h" + +// superclass for each half of the game (player or controller) +class GameHalf : public MouseHandlerGL, + public KeyboardHandlerGL, + public SceneHandlerGL, + public RedrawListenerGL { + + public: + + GameHalf(); + + virtual ~GameHalf(); + + + virtual void setScreen( ScreenGL *inScreen ) { + mScreen = inScreen; + } + + + // true if game is trying to quit + virtual char isQuitting(); + + + + virtual void resetTimer() = 0; + + + + virtual void step() = 0; + + // implements the SceneHandlerGL interface + virtual void drawScene() = 0; + + // redraw listener, defaults to doing nothing + virtual void fireRedraw() {} + virtual void postRedraw() {} + + + + // empty implementations of each input function, + // in case subclasses don't need them + + // implements the MouseHandlerGL interface + virtual void mouseMoved( int inX, int inY ) {} + + virtual void mouseDragged( int inX, int inY ); + virtual void mousePressed( int inX, int inY ); + virtual void mouseReleased( int inX, int inY ); + + // implements the KeyboardHandlerGL interface + virtual char isFocused() { + return true; + } + // handles quit and hardQuit mode + virtual void keyPressed( unsigned char inKey, int inX, int inY ); + + virtual void specialKeyPressed( int inKey, int inX, int inY ) {} + virtual void keyReleased( unsigned char inKey, int inX, int inY ) {} + virtual void specialKeyReleased( int inKey, int inX, int inY ) {} + + + protected: + + ScreenGL *mScreen; + + int mSecondsPerMove; + int mStepsPerSecond; + int mStepsLeft; + + double mLastStepTime; + + + void decrementStepCount(); + + + + char mQuitting; + + }; + + + +#endif diff --git a/gameSource/GameState.cpp b/gameSource/GameState.cpp new file mode 100644 index 0000000..02b4f8b --- /dev/null +++ b/gameSource/GameState.cpp @@ -0,0 +1,956 @@ +#include "GameState.h" +#include "Music.h" +#include "Song.h" +#include "Scene.h" +#include "SongPicker.h" +#include "SongEditor.h" +#include "speechHints.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + +#include + + + +extern SongPicker *mainSongPicker; +extern SongEditor *mainSongEditor; + + + +char GameState::sNoteToggleBuffer[N][N]; + + + +GameState::GameState() { + mRoomTrans = 255; + + mObjectZeroFrozen = false; + + mLocksOn = true; + + + // must have object 0 always + StateObject obj; + + newObject( G / 2, G / 2, obj ); + + mSelectedObject = 0; + } + + +GameState::~GameState() { + for( int i=0; imRoom = mRoom; + s->mRoomTrans = mRoomTrans; + + s->mObjectZeroFrozen = mObjectZeroFrozen; + + s->mLocksOn = mLocksOn; + + + // default s has one object in it + // clear it + int i; + for( i=0; imObjects.size(); i++ ) { + delete *( s->mObjects.getElement( i ) ); + } + s->mObjects.deleteAll(); + + + for( i=0; imObjects.push_back( otherObject->copy() ); + } + + s->mSelectedObject = mSelectedObject; + + return s; + } + + + +GameState::GameState( unsigned char *inMessage, int inLength ) { + + + int numUsed; + + + + if( inLength < N * N ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + + int i=0; + for( int y=0; ylogPrintf( + Log::DETAIL_LEVEL, + "Parsing message for state with %d objects\n", numObjects ); + + + for( int i=0; imObjectTrans = (char)inMessage[0]; + + inLength -= 1; + inMessage = &( inMessage[ 1 ] ); + + + + o->mAnchorPosition = readIntPair( inMessage, inLength, + &numUsed ); + if( numUsed == -1 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + inLength -= numUsed; + inMessage = &( inMessage[ numUsed ] ); + + + + o->mSpeechOffset = readIntPair( inMessage, inLength, + &numUsed ); + if( numUsed == -1 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + inLength -= numUsed; + inMessage = &( inMessage[ numUsed ] ); + + + + o->mActionOffset = readIntPair( inMessage, inLength, + &numUsed ); + if( numUsed == -1 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + inLength -= numUsed; + inMessage = &( inMessage[ numUsed ] ); + + + + if( inLength < 4 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + + o->mSpeechFlip = (char)( inMessage[0] ); + o->mSpeechBox = (char)( inMessage[1] ); + o->mLocked = (char)( inMessage[2] ); + + int speechLength = inMessage[3]; + + inLength -= 4; + inMessage = &( inMessage[4] ); + + + setSpeechHint( id, o->mSpeechOffset, o->mSpeechFlip ); + + + if( inLength < speechLength ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + + delete [] o->mSpokenMessage; + o->mSpokenMessage = new char[ speechLength + 1 ]; + memcpy( o->mSpokenMessage, inMessage, speechLength ); + o->mSpokenMessage[speechLength] = '\0'; + + inLength -= speechLength; + inMessage = &( inMessage[ speechLength ] ); + + + if( inLength < 11 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + + memcpy( o->mAction, inMessage, 11 ); + inLength -= 11; + inMessage = &( inMessage[ 11 ] ); + + //printf( " " ); + //o->print(); + } + + + mHasSong = false; + + // ignore the possibility of this being present if we are Controller + // song should never be sent by Player, but is sometimes due to a v14 + // bug. + if( inLength >= U && mainSongEditor == NULL ) { + // song ID present? + uniqueID id = readUniqueID( inMessage, inLength, &numUsed ); + + if( numUsed == -1 ) { + AppLog::error( "Failed to parse GameState from message\n" ); + return; + } + inLength -= numUsed; + inMessage = &( inMessage[ numUsed ] ); + + // load this Song, to force it to be fetched from network + Song s( id ); + + // from network, but don't save it, because it was live on Controller + // side (repeat same ID, even though it doesn't match---this + // live song has no ID, because it's never been saved) + Song liveSong( id, inMessage, inLength, true, false ); + + mLiveSongReceived = liveSong; + + mHasSong = true; + } + + + /* + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "After parse, %d bytes left over\n", inLength ); + */ + mSelectedObject = 0; + } + + + +char GameState::hasSong() { + // for now + return mHasSong; + } + + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; + + + + +unsigned char *GameState::getStateAsMessage( int *outLength ) { + + // NOTE: music is a little inelegant, but that's because + // it is *separate* from the game state (not undone, not bookmarked, + // etc, along with the game state) + + // format: + + // NxN note toggles (actual, live music state, maybe not yet saved) + // Music ID (saved music selected in music picker) + // Room ID + // num objects + // each object: + // StateObject ID + // object trans (0..255, 1 char) + // anchor pos (as 8-byte int pair) + // speech offset (as 8-byte int pair) + // speech flip (1 char) + // speech length (1 char) + // speech chars + // -- v14 stuff at end (so v13 can ignore it): -- + // NOTE: these are ONLY present in messages sent FROM Controller + // Song ID of last saved song + // Inline version of live (perhaps not saved) Song object + + + SimpleVector messageAccum; + + + // for v13 backwards compat + // send across first bar of first instrument + for( int y=0; ylogPrintf( + Log::DETAIL_LEVEL, + "Generating message for state with %d objects\n", + numObjects ); + + for( int i=0; iprint(); + + id = o->mObject.getUniqueID(); + + messageAccum.push_back( id.bytes, U ); + + messageAccum.push_back( o->mObjectTrans ); + + messageAccum.push_back( getChars( o->mAnchorPosition ), 8 ); + messageAccum.push_back( getChars( o->mSpeechOffset ), 8 ); + messageAccum.push_back( getChars( o->mActionOffset ), 8 ); + + messageAccum.push_back( (unsigned char)( o->mSpeechFlip ) ); + messageAccum.push_back( (unsigned char)( o->mSpeechBox ) ); + messageAccum.push_back( (unsigned char)( o->mLocked ) ); + + int speechLength = strlen( o->mSpokenMessage ); + + messageAccum.push_back( (unsigned char)speechLength ); + + messageAccum.push_back( (unsigned char *)( o->mSpokenMessage ), + speechLength ); + messageAccum.push_back( (unsigned char *)( o->mAction ), + 11 ); + } + + + if( mainSongEditor != NULL ) { + + // ID of selected, saved song + uniqueID songID = mainSongPicker->getSelectedResourceID(); + + messageAccum.push_back( songID.bytes, U ); + + + int numBytes; + unsigned char *liveSongBytes = + mainSongEditor->getLiveSong().makeBytes( &numBytes ); + + messageAccum.push_back( liveSongBytes, numBytes ); + delete [] liveSongBytes; + } + + + + *outLength = messageAccum.size(); + + + return messageAccum.getElementArray(); + } + + + + + + + + +int GameState::getHitObject( int inGridX, int inGridY ) { + // top objects first + int numObjects = mObjects.size(); + + // but consider 0 first + StateObjectInstance *o = *( mObjects.getElement( 0 ) ); + + if( o->mAnchorPosition.x / P == inGridX + && + o->mAnchorPosition.y / P == inGridY ) { + return 0; + } + + + + + // always consider unlocked before locked + for( int i=numObjects-1; i>=0; i-- ) { + StateObjectInstance *o = *( mObjects.getElement( i ) ); + + if( ! o->mLocked ) { + + if( o->mAnchorPosition.x / P == inGridX + && + o->mAnchorPosition.y / P == inGridY ) { + return i; + } + } + } + + if( !mLocksOn ) { + + // now consider locked, since no unlocked were hit + for( int i=numObjects-1; i>=0; i-- ) { + StateObjectInstance *o = *( mObjects.getElement( i ) ); + + if( o->mLocked ) { + + if( o->mAnchorPosition.x / P == inGridX + && + o->mAnchorPosition.y / P == inGridY ) { + return i; + } + } + } + } + + return -1; + } + + +int GameState::getSelectedObject() { + return mSelectedObject; + } + + +void GameState::setSelectedObject( int inSelected ) { + mSelectedObject = inSelected; + } + + +// to right a bit, centered vertically +static intPair defaultAnchorOffset = { P/2 + P, P/2 }; + + + +void GameState::moveSelectedObject( int inGridX, int inGridY ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + + intPair oldPos = o->mAnchorPosition; + + intPair newPos = { inGridX * P, inGridY * P }; + + intPair deltaPos = subtract( newPos, oldPos ); + + // subtract this delta from action offset so that it does NOT move + // along with object (only if action has text in it AND anchor is not + // at starting pos [if at starting pos, it moves with object]) + if( strlen( o->mAction ) > 0 && + ! equals( o->mActionOffset, defaultAnchorOffset ) ) { + + o->mActionOffset = subtract( o->mActionOffset, deltaPos ); + } + + o->mAnchorPosition = newPos; + } + + +void GameState::moveSelectedSpeechAnchor( int inPixelX, int inPixelY ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + intPair absPos = { inPixelX, inPixelY }; + + o->mSpeechOffset = subtract( absPos, o->mAnchorPosition ); + + setSpeechHint( o->mObject.getUniqueID(), o->mSpeechOffset, + o->mSpeechFlip ); + } + + + +void GameState::moveSelectedActionAnchor( int inPixelX, int inPixelY ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + intPair absPos = { inPixelX, inPixelY }; + + o->mActionOffset = subtract( absPos, o->mAnchorPosition ); + } + + +void GameState::resetActionAnchor( int inObjectIndex ) { + StateObjectInstance *o = *( mObjects.getElement( inObjectIndex ) ); + + // to right a bit, centered vertically + o->mActionOffset = defaultAnchorOffset; + } + + + + +char GameState::canAdd() { + return( mObjects.size() < 255 ); + } + + + + +void GameState::newObject( int inGridX, int inGridY, StateObject inObject ) { + if( mObjects.size() >= 255 ) { + AppLog::error( "Error: adding another object to a full state!\n" ); + } + else { + + StateObjectInstance *o = new StateObjectInstance( inObject ); + + // centered + o->mAnchorPosition.x = P * inGridX; + o->mAnchorPosition.y = P * inGridY; + + o->mSpeechOffset = getSpeechHint( inObject.getUniqueID(), + &( o->mSpeechFlip ) ); + //o->mSpeechOffset.y = P/2; + + // no sprites + mObjects.push_back( o ); + + + mSelectedObject = mObjects.size() - 1; + + resetActionAnchor( mSelectedObject ); + } + + } + + + +void GameState::deleteSelectedObject() { + // never delete obj 0 + if( mSelectedObject > 0 ) { + + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + delete o; + mObjects.deleteElement( mSelectedObject ); + + setSelectedObject( mSelectedObject - 1 ); + + if( mLocksOn ) { + // look for an unlocked object to make selected + while( getSelectedLocked() ) { + setSelectedObject( mSelectedObject - 1 ); + } + } + + } + } + + + +void GameState::changeSelectedObject( StateObject inObject ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + o->mObject = inObject; + } + + + +void GameState::adjustSelectedTrans( unsigned char inTrans ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + o->mObjectTrans = inTrans; + } + + + +unsigned char GameState::getSelectedTrans() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + return o->mObjectTrans; + } + + + +void GameState::addCharToSelectedSpeech( char inChar ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + if( strlen( o->mSpokenMessage ) < 255 ) { + + char *newString = autoSprintf( "%s%c", o->mSpokenMessage, inChar ); + delete [] o->mSpokenMessage; + o->mSpokenMessage = newString; + + o->mSpeechStale = false; + + //printf( "Adding %d char\n", strlen( o->mSpokenMessage ) ); + } + else { + AppLog::error( "Error: adding 256th character to speech\n" ); + } + } + + +void GameState::deleteCharFromSelectedSpeech() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + int oldLength = strlen( o->mSpokenMessage ); + + if( oldLength > 0 ) { + // terminate one char sooner + o->mSpokenMessage[ oldLength - 1 ] = '\0'; + o->mSpeechStale = false; + } + } + + +int GameState::getSelectedSpeechLength() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + return strlen( o->mSpokenMessage ); + } + + +void GameState::deleteAllCharsFromSelectedSpeech() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + delete [] o->mSpokenMessage; + o->mSpokenMessage = stringDuplicate( "" ); + o->mSpeechStale = false; + } + + + +void GameState::flipSelectedSpeech() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + o->mSpeechFlip = ! o->mSpeechFlip; + + setSpeechHint( o->mObject.getUniqueID(), + o->mSpeechOffset, o->mSpeechFlip ); + } + + + +void GameState::setSelectedSpeechBox( char inBox ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + o->mSpeechBox = inBox; + } + + + +void GameState::setSelectedLocked( char inLocked ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + o->mLocked = inLocked; + } + + + +char GameState::getSelectedLocked() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + return o->mLocked; + } + + + +void GameState::setObjectHeld( int inObject, char inHeld ) { + StateObjectInstance *o = *( mObjects.getElement( inObject ) ); + o->mHeld = inHeld; + } + + + +char GameState::getObjectHeld( int inObject ) { + StateObjectInstance *o = *( mObjects.getElement( inObject ) ); + return o->mHeld; + } + + + + + +int GameState::getAutoOffsetOnFlip( int inObject ) { + // heuristic: + // if offset is less that one gridspace away from object center, + // in direction of manual flip, reverse the offset on the other side + // when we auto-flip + + StateObjectInstance *o = *( mObjects.getElement( inObject ) ); + + int offset = o->mSpeechOffset.x; + + int autoFlipExtraOffset = 0; + + if( !o->mSpeechFlip && offset >=P/2 && offset <= P + P/2 ) { + autoFlipExtraOffset = -2 * ( offset - P/2) + 1 - 2; + } + else if( o->mSpeechFlip && offset <=P/2 && offset >= P/2 - P ) { + autoFlipExtraOffset = -2 * ( offset - P/2) + 1 - 2; + } + + return autoFlipExtraOffset; + } + + + + +void GameState::deleteAllNonPlayerSpeech() { + int numObjects = mObjects.size(); + + for( int i=1; imSpeechStale ) { + delete [] o->mSpokenMessage; + o->mSpokenMessage = stringDuplicate( "" ); + o->mSpeechStale = false; + } + } + } + + + +void GameState::markNonPlayerSpeechStale() { + int numObjects = mObjects.size(); + + for( int i=1; imSpeechStale = true; + } + } + + + +void GameState::addCharToSelectedAction( char inChar ) { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + + int oldLen = strlen( o->mAction ); + if( oldLen < 10 ) { + + o->mAction[ oldLen ] = inChar; + o->mAction[ oldLen + 1 ] = '\0'; + } + else { + //printf( "Error: adding 11th character to action\n" ); + } + } + + +void GameState::deleteCharFromSelectedAction() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + int oldLength = strlen( o->mAction ); + + if( oldLength > 0 ) { + // terminate one char sooner + o->mAction[ oldLength - 1 ] = '\0'; + } + } + + +int GameState::getSelectedActionLength() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + return strlen( o->mAction ); + } + + +void GameState::deleteAllCharsFromSelectedAction() { + StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); + o->mAction[0] = '\0'; + } + + + +void GameState::freezeObjectZero( char inFrozen ) { + mObjectZeroFrozen = inFrozen; + } + + +char GameState::isObjectZeroFrozen() { + return mObjectZeroFrozen; + } + + + + + + + + + + + + +static intPair zeroPair = {0,0}; + + +StateObjectInstance::StateObjectInstance( StateObject inObject ) + : mObject( inObject ), mObjectTrans( 255 ), + mAnchorPosition( zeroPair ), + mSpeechOffset( zeroPair ), + mActionOffset( zeroPair ), + mSpeechFlip( 0 ), + mSpeechBox( 0 ), + mLocked( 0 ), + mHeld( 0 ), + mSpokenMessage( stringDuplicate( "" ) ), + mSpeechStale( false ) { + + mAction[0] = '\0'; + } + + + +StateObjectInstance::~StateObjectInstance() { + delete [] mSpokenMessage; + } + + + +StateObjectInstance *StateObjectInstance::copy() { + StateObjectInstance *o = new StateObjectInstance( mObject ); + + delete [] o->mSpokenMessage; + + o->mObjectTrans = mObjectTrans; + o->mAnchorPosition = mAnchorPosition; + o->mSpeechOffset = mSpeechOffset; + o->mActionOffset = mActionOffset; + + o->mSpeechFlip = mSpeechFlip; + o->mSpeechBox = mSpeechBox; + o->mLocked = mLocked; + o->mHeld = mHeld; + + o->mSpokenMessage = stringDuplicate( mSpokenMessage ); + + o->mSpeechStale = mSpeechStale; + + + // init mAction first so that we don't leave uninitialized values in + // end of buffer + memset( (void *)( o->mAction ), '\0', 11 ); + + memcpy( o->mAction, mAction, strlen( mAction ) + 1 ); + + return o; + } + + +void StateObjectInstance::print() { + printf( "State object at " ); + ::print( mAnchorPosition ); + printf( "\n" ); + } + + + + + + diff --git a/gameSource/GameState.h b/gameSource/GameState.h new file mode 100644 index 0000000..2fe85c7 --- /dev/null +++ b/gameSource/GameState.h @@ -0,0 +1,202 @@ +#ifndef GAME_STATE_INCLUDED +#define GAME_STATE_INCLUDED + + +#include "color.h" +#include "common.h" +#include "uniqueID.h" + +#include "Room.h" +#include "StateObject.h" +#include "Song.h" + +#include "musicPlayer.h" + + +#include "minorGems/util/SimpleVector.h" + + + +class StateObjectInstance { + public: + StateObjectInstance( StateObject inObject ); + + ~StateObjectInstance(); + + StateObjectInstance *copy(); + + + StateObject mObject; + + unsigned char mObjectTrans; + + + intPair mAnchorPosition; + intPair mSpeechOffset; + intPair mActionOffset; + + char mSpeechFlip; + + char mSpeechBox; + + char mLocked; + char mHeld; + + char *mSpokenMessage; + + char mAction[11]; + + void print(); + + + char mSpeechStale; + }; + + + +class GameState { + public: + + // default empty state + GameState(); + + // load state from a message (destroyed by caller) + GameState( unsigned char *inMessage, int inLength ); + + ~GameState(); + + GameState *copy(); + + + Room mRoom; + + unsigned char mRoomTrans; + + + SimpleVector mObjects; + + char mObjectZeroFrozen; + + // music ID to be sent along with this state when it is encoded + // as a message + uniqueID mMusicID; + + // scene ID to be sent along with this state when it is encoded + // as a message---last selected scene from picker, even if it + // doesn't match current scene state + uniqueID mSceneID; + + + char mLocksOn; + + + unsigned char *getStateAsMessage( int *outLength ); + + + int getSelectedObject(); + + void setSelectedObject( int inSelected ); + + + int getHitObject( int inGridX, int inGridY ); + + void moveSelectedObject( int inGridX, int inGridY ); + + void moveSelectedSpeechAnchor( int inPixelX, int inPixelY ); + + void moveSelectedActionAnchor( int inPixelX, int inPixelY ); + + void resetActionAnchor( int inObjectIndex ); + + + // room for one more? + char canAdd(); + + + // becomes selected + void newObject( int inGridX, int inGridY, StateObject inObject ); + + void deleteSelectedObject(); + + + void changeSelectedObject( StateObject inObject ); + + + void adjustSelectedTrans( unsigned char inTrans ); + unsigned char getSelectedTrans(); + + + void addCharToSelectedSpeech( char inChar ); + void deleteCharFromSelectedSpeech(); + + int getSelectedSpeechLength(); + void deleteAllCharsFromSelectedSpeech(); + + void flipSelectedSpeech(); + + void setSelectedSpeechBox( char inBox ); + + void setSelectedLocked( char inLocked ); + + char getSelectedLocked(); + + + void setObjectHeld( int inObject, char inHeld ); + + char getObjectHeld( int inObject ); + + + // amount x position of speech should be shifted on a flip, + // based on a heuristic + int getAutoOffsetOnFlip( int inObject ); + + + // deletes speech from every object but 0 + void deleteAllNonPlayerSpeech(); + + // sets as stale, for clear by deleteAllNonPlayerSpeech later + // however, if speech is edited after this, it becomes non-stale again, + // and won't be deleted by deleteAllNonPlayerSpeech + void markNonPlayerSpeechStale(); + + + + void addCharToSelectedAction( char inChar ); + void deleteCharFromSelectedAction(); + + int getSelectedActionLength(); + void deleteAllCharsFromSelectedAction(); + + + void freezeObjectZero( char inFrozen ); + char isObjectZeroFrozen(); + + + // true if this received game state has a Song in it (v14) + // or false if it uses the old (v13) note grid + // So v14 and v13 can play together + char hasSong(); + + + // where last received note toggles are stored + // (from states parsed from messages) + static char sNoteToggleBuffer[N][N]; + + // where last received live song is stored + Song mLiveSongReceived; + + + protected: + + + int mSelectedObject; + + char mHasSong; + + + + }; + + + +#endif diff --git a/gameSource/GameStateDisplay.cpp b/gameSource/GameStateDisplay.cpp new file mode 100644 index 0000000..ef101e3 --- /dev/null +++ b/gameSource/GameStateDisplay.cpp @@ -0,0 +1,1537 @@ +#include "GameStateDisplay.h" + +#include "common.h" +#include "SpriteResource.h" +#include "GameStateEditor.h" + +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/util/stringUtils.h" + + +extern TextGL *largeText; + + + +GameStateDisplay::GameStateDisplay( int inAnchorX, int inAnchorY, + char inShowObjectToolTips ) + : GUIComponentGL( inAnchorX, inAnchorY, + G * P, G * P ), + mHasAnySpeechBeenAutoFlipped( false ), + mManualBubblePositioningLive( false ), + mLastPixelClickX( 0 ), mLastPixelClickY( 0 ), + mLastGridClickX( 0 ), mLastGridClickY( 0 ), + mLastActionRelease( false ), + mLastActionPress( false ), + mLastActionKeyPress( false ), + mLastMouseoverObjectIndex( -1 ), + mMouseHover( false ), + mLastHoverGridX( 0 ), mLastHoverGridY( 0 ), + mEditingSpeech( false ), + mEditingAction( false ), + mHighlightEditedBubble( false ), + mShowObjectToolTips( inShowObjectToolTips ), + mEditor( NULL ), + mFocused( false ), + mShowGrid( true ), + mHintMode( false ), + mState( NULL ), + mLastSelected( -1 ), + mBlinkCycle( 0 ) { + + + for( int y=0; ymRoom.getTile( x, y ) ); + + mSprites[y][x] = t.getSprite(); + } + } + + + // clear old + int numSprites = mObjectSprites.size(); + int i; + for( i=0; imObjects.size(); + + // flag for 0 + mSpeechOffTop.push_back( false ); + + + // add 0 (player object) last so it's always on top + for( i=1; imObjects.getElement( i ) ); + + + int numSprites = o->mObject.getNumLayers(); + + intPair anchorPos = o->mAnchorPosition; + + int depth = G - (anchorPos.y / P) - 1; + + for( int s=0; smObject.getLayerSprite( s ) ); + + mObjectSprites.push_back( resource.getSprite() ); + + + intPair p = o->mObject.getLayerOffset( s ); + + p = ::add( p, anchorPos ); + + mObjectSpritePositions.push_back( p ); + // no owner flags for non-anchors + mObjectSpriteOwners.push_back( -1 ); + + float trans = + ( o->mObjectTrans / 255.0f ) * + ( o->mObject.getLayerTrans( s ) / 255.0f ); + + mObjectSpriteTrans.push_back( trans ); + + mObjectSpriteGlows.push_back( o->mObject.getLayerGlow( s ) ); + + mObjectSpriteDepth.push_back( depth ); + + mObjectSpriteLocked.push_back( o->mLocked ); + } + + // last, add anchor sprite for this object, on top + if( i==0 ) { + // special anchor for player + mObjectSprites.push_back( new Sprite( "playerAnchor.tga", true ) ); + } + else { + mObjectSprites.push_back( new Sprite( "anchor.tga", true ) ); + } + + mObjectSpritePositions.push_back( o->mAnchorPosition ); + // only "own" the anchors + mObjectSpriteOwners.push_back( i ); + + // anchors ignore trans and glow + mObjectSpriteTrans.push_back( 1.0 ); + mObjectSpriteGlows.push_back( false ); + + // anchors are all on top + mObjectSpriteDepth.push_back( G ); + + mObjectSpriteLocked.push_back( o->mLocked ); + } + + + +int GameStateDisplay::updatePositionFromStateInstance( int inIndex, + int inSpriteIndex ) { + int i = inIndex; + + + StateObjectInstance *o = *( mState->mObjects.getElement( i ) ); + + + int numSprites = o->mObject.getNumLayers(); + + intPair anchorPos = o->mAnchorPosition; + + int depth = G - (anchorPos.y / P) - 1; + + for( int s=0; smObject.getLayerOffset( s ); + + p = ::add( p, anchorPos ); + + *( mObjectSpritePositions.getElement( inSpriteIndex ) ) = p; + + float trans = + ( o->mObjectTrans / 255.0f ) * + ( o->mObject.getLayerTrans( s ) / 255.0f ); + + *( mObjectSpriteTrans.getElement( inSpriteIndex ) ) = trans; + + *( mObjectSpriteGlows.getElement( inSpriteIndex ) ) = + o->mObject.getLayerGlow( s ); + + *( mObjectSpriteDepth.getElement( inSpriteIndex ) ) = depth; + + *( mObjectSpriteLocked.getElement( inSpriteIndex ) ) = o->mLocked; + + inSpriteIndex++; + } + + + *( mObjectSpritePositions.getElement( inSpriteIndex ) ) = + o->mAnchorPosition; + + *( mObjectSpriteTrans.getElement( inSpriteIndex ) ) = 1.0; + + *( mObjectSpriteGlows.getElement( inSpriteIndex ) ) = false; + + *( mObjectSpriteLocked.getElement( inSpriteIndex ) ) = o->mLocked; + + + inSpriteIndex++; + + + return inSpriteIndex; + } + + + +void GameStateDisplay::updateSpritePositions( GameState *inState ) { + + if( mState != NULL ) { + delete mState; + } + + mState = inState; + + + int numObjects = mState->mObjects.size(); + + int spriteIndex = 0; + + // 0 last (player object on top) + for( int i=1; i depthBins[ G + 1 ]; + + int numSprites = mObjectSprites.size(); + + + // put locked ones in first (behind) for each depth + for( int i=0; imObjects.size(); + + for( int i=0; imObjects.getElement( i ) ); + + StateObjectInstance *oDest = + *( inDestinationState->mObjects.getElement( i ) ); + + + oDest->mSpeechFlip = o->mSpeechFlip; + oDest->mSpeechOffset = o->mSpeechOffset; + } + } + + + +char GameStateDisplay::getSpeechOffTop( int inObject ) { + return *( mSpeechOffTop.getElement( inObject ) ); + } + + + + +void GameStateDisplay::drawSpeech( int inIndex ) { + int i = inIndex; + + int selected = mState->getSelectedObject(); + + Color *bubbleColor = NULL; + + + StateObjectInstance *o = *( mState->mObjects.getElement( i ) ); + + // skip any blank messages + // UNLESS we are editing text of selected + if( strlen( o->mSpokenMessage ) > 0 + || + ( i == selected && mEditingSpeech ) ) { + + + if( i == selected && isFocused() && mHighlightEditedBubble ) { + bubbleColor = new Color( 1.0f, 1.0f, 0.5f, 1.0f ); + } + + + intPair pos = ::add( o->mSpeechOffset, o->mAnchorPosition ); + + double height = 8; + double width = strlen( o->mSpokenMessage ) * height; + + + double trueStringHeight = height * + largeText->measureTextHeight( o->mSpokenMessage ); + + char bubbleHasHighLines = false; + char bubbleHasReallyHighLines = false; + + if( trueStringHeight > 8 ) { + // make room for high characters + bubbleHasHighLines = true; + } + if( trueStringHeight > 9 ) { + bubbleHasReallyHighLines = true; + } + + + + int bubbleWidth = 64 - 8; + + + + + SimpleVector lines; + + + // new code: split into words first, ingore player's spacing. + // 1 space between words, two spaces after a period + SimpleVector *words = tokenizeString( o->mSpokenMessage ); + + int wordIndex = 0; + int numWords = words->size(); + + while( wordIndex < numWords ) { + // a word left + char *nextWord = *( words->getElement( wordIndex ) ); + double nextWordWidth = + largeText->measureTextWidth( nextWord ) * height; + + char *currentLine = NULL; + double currentWidth = 0; + int numLines = lines.size(); + + if( numLines > 0 ) { + + currentLine = *( lines.getElement( numLines - 1 ) ); + currentWidth = + largeText->measureTextWidth( currentLine ) * height; + } + + char *spaceNeeded = (char *)" "; + int lastCharIndex = -1; + if( currentLine != NULL ) { + lastCharIndex = strlen( currentLine ) - 1; + } + + if( currentLine != NULL && + ( currentLine[ lastCharIndex ] == '.' || + currentLine[ lastCharIndex ] == '?' || + currentLine[ lastCharIndex ] == '!' ) ) { + // line so far ends with period (or other sentence ender), + // extra space before next word in line + spaceNeeded = (char *)" "; + } + double spaceWidth = + largeText->measureTextWidth( spaceNeeded ) * height; + + if( currentLine != NULL && + currentWidth + spaceWidth + nextWordWidth + < bubbleWidth ) { + + // good! we can add it + char *newLine = autoSprintf( "%s%s%s", currentLine, + spaceNeeded, nextWord ); + delete [] currentLine; + *( lines.getElement( numLines - 1 ) ) = newLine; + + delete [] nextWord; + } + else { + // not enough room on this line, or no line yet + // start a new one with just this word + + if( nextWordWidth < bubbleWidth ) { + lines.push_back( nextWord ); + } + else { + // problem! really long word + + // split it into parts... make sure second part has at + // least 3 chars + + char *partialWord = stringDuplicate( nextWord ); + + double hypenWidth = largeText->measureTextWidth( "-" ); + + while( ( hypenWidth + + largeText->measureTextWidth( partialWord ) ) + * height + >= bubbleWidth ) { + + // truncate + partialWord[ strlen( partialWord ) - 1 ] = '\0'; + } + + int partialLength = strlen( partialWord ); + + int nextWordLength = strlen( nextWord ); + + int extraLength = nextWordLength - partialLength; + + while( extraLength < 3 ) { + // trim more off partial + partialWord[ partialLength - 1 ] = '\0'; + + partialLength -= 1; + + extraLength ++; + } + + // add hyphen + partialWord[ partialLength ] = '-'; + partialWord[ partialLength + 1 ] = '\0'; + + lines.push_back( partialWord ); + + char *extraChars = stringDuplicate( + &( nextWord[ partialLength ] ) ); + + *( words->getElement( wordIndex ) ) = extraChars; + delete [] nextWord; + + // part of the word still there, back up and look + // at it again + wordIndex --; + } + } + wordIndex ++; + } + delete words; + + + + + + // start at bottom line and draw up + double bubbleFade = 0.63; + + int flipSign = 1; + + if( o->mSpeechFlip ) { + flipSign = -1; + } + + + // draw bottom w/ tip at anchor + Vector3D bubblePos( mAnchorX + pos.x + flipSign * 32, + mAnchorY + pos.y + 7, + 0 ); + + + + if( o->mSpeechBox ) { + bubblePos.mY -= 8; + } + + + int numLines = lines.size(); + + int bubbleHeight = 7 + 9 * numLines + 7; + + if( bubbleHasHighLines ) { + + if( bubbleHasReallyHighLines ) { + // use extended mode + bubbleHeight = 7 + 12 * numLines + 7; + } + else { + // thin extended mode + bubbleHeight = 7 + 10 * numLines + 7; + } + } + + + char offTop = false; + + if( bubblePos.mY + bubbleHeight > mHeight + mAnchorY ) { + offTop = true; + } + + char oldOffTop = *( mSpeechOffTop.getElement( i ) ); + + + *( mSpeechOffTop.getElement( i ) ) = offTop; + + if( offTop != oldOffTop ) { + // a change in off-top status. Tell editor about it + mHasAnySpeechBeenAutoFlipped = true; + } + + + // auto-flip if off the side of screen + + char flipThisBubble = o->mSpeechFlip; + + int bubbleHalfWidth = 32; + int bubbleFullWidth = 64; + + // off-top boxes are no wider + if( ! o->mSpeechBox && + offTop && numLines > 0 ) { + bubbleHalfWidth += 10; + } + + + // apply flipping heuristic positioning + int autoFlipExtraOffset = mState->getAutoOffsetOnFlip( i ); + + if( mManualBubblePositioningLive ) { + // don't mess with the mouse, drop heuristic and go with + // mouse position + autoFlipExtraOffset = 0; + } + + + + if( o->mSpeechFlip && bubblePos.mX - bubbleHalfWidth < mAnchorX ) { + // unflip + flipThisBubble = false; + bubblePos.mX += bubbleFullWidth; + bubblePos.mX += autoFlipExtraOffset; + + // auto-flip this bubble for the future, too, so that mouse-based + // positioning + // works correctly, etc + o->mSpeechFlip = false; + o->mSpeechOffset.x += autoFlipExtraOffset; + // thus, we WON'T have to auto-flip next time we draw this same + // frame + mHasAnySpeechBeenAutoFlipped = true; + } + else if( ! o->mSpeechFlip && bubblePos.mX + bubbleHalfWidth > + mWidth + mAnchorX ) { + // force a flip + flipThisBubble = true; + bubblePos.mX -= bubbleFullWidth; + bubblePos.mX += autoFlipExtraOffset; + + // again, permanently re-flip + o->mSpeechFlip = true; + o->mSpeechOffset.x += autoFlipExtraOffset; + mHasAnySpeechBeenAutoFlipped = true; + } + + + if( o->mSpeechFlip ) { + bubblePos.mX += 1; + } + + + Sprite *bottomSprite = mBubbleBottomSprite; + Sprite *middleTailSprite = mBubbleMiddleTailSprite; + + + if( flipThisBubble ) { + bottomSprite = mBubbleBottomSpriteFlip; + middleTailSprite = mBubbleMiddleTailFlipSprite; + } + if( o->mSpeechBox ) { + bottomSprite = mSpeechBoxBottomSprite; + // no middle tails + middleTailSprite = mBubbleMiddleSprite; + } + + + int lineWithTail = 0; + + if( offTop ) { + if( !o->mSpeechBox ) { + + if( numLines > 0 ) { + bottomSprite = mBubbleBottomNoTailSprite; + // scoot over to make room for horizontal tail + if( flipThisBubble ) { + bubblePos.mX -= 10; + } + else { + bubblePos.mX += 10; + } + + + + + + // scoot the whole thing down by some number of lines + int linesToScoot = numLines; + //lineWithTail = numLines - 1; + + while( bubblePos.mY + bubbleHeight > mHeight + mAnchorY + && + linesToScoot > 0 ) { + bubblePos.mY -= 9; + if( linesToScoot == numLines ) { + // anchor pos for bubble is above bottom tail, now + // up aligned with a line instead. + bubblePos.mY -= 10; + } + + linesToScoot --; + } + //bubblePos.mY -= 10; + + // how many lines left? + lineWithTail = linesToScoot; + } + } + else { + // simply flush boxes with top... no tail to align! + bubblePos.mY -= + (bubblePos.mY + bubbleHeight) + - (mHeight + mAnchorY); + } + + } + + + + bottomSprite->draw( 0, 0, &bubblePos, 1, + bubbleFade, + bubbleColor ); + + bubblePos.mY += 4; + + // draw lines of text last, to ensure they're on top of bubble parts + double *lineY = new double[numLines]; + + + for( int i=numLines-1; i>=0; i-- ) { + bubblePos.mY += 8; + + if( i < numLines - 1 ) { + // extra space for all but bottom line + bubblePos.mY += 1; + } + if( bubbleHasHighLines ) { + // extended line height mode + + if( i < numLines - 1 ) { + // extra space for all but bottom line + bubblePos.mY += 1; + + if( bubbleHasReallyHighLines ) { + bubblePos.mY += 2; + } + } + // padding above all but top line + if( i > 0 ) { + + if( bubbleHasReallyHighLines ) { + + mBubbleMiddleExtraSprite->draw( 0, 0, &bubblePos, 1, + bubbleFade, + bubbleColor ); + } + else { + mBubbleMiddleExtraThinSprite->draw( + 0, 0, &bubblePos, 1, + bubbleFade, + bubbleColor ); + } + } + + } + + + if( offTop && lineWithTail == i ) { + // normal line + middleTailSprite->draw( 0, 0, &bubblePos, 1, + bubbleFade, + bubbleColor ); + } + else { + // normal line + mBubbleMiddleSprite->draw( 0, 0, &bubblePos, 1, + bubbleFade, + bubbleColor ); + } + + lineY[i] = bubblePos.mY - 4; + /* + char *line = *( lines.getElement( i ) ); + + width = strlen( line ) * height; + + glColor4f( 0, 0, 0, 1 ); + + largeText->drawText( line, + bubblePos.mX - 32 + 4, + bubblePos.mY - 4, + width, height ); + delete [] line; + */ + } + + bubblePos.mY += 7; + if( numLines > 0 ) { + // extra space before top + bubblePos.mY += 1; + } + + if( o->mSpeechBox ) { + // flip, because this one is upside down in file (so lower + // corner can have transparency) + mSpeechBoxTopSprite->draw( 0, 0, &bubblePos, -1, bubbleFade, + bubbleColor ); + } + else { + mBubbleTopSprite->draw( 0, 0, &bubblePos, 1, bubbleFade, + bubbleColor ); + } + + // lines last + for( int i=numLines-1; i>=0; i-- ) { + + char *line = *( lines.getElement( i ) ); + + width = strlen( line ) * height; + + glColor4f( 0, 0, 0, 1 ); + + largeText->drawText( line, + bubblePos.mX - 32 + 4, + lineY[i], + width, height ); + delete [] line; + } + delete []lineY; + } + + + + if( bubbleColor != NULL ) { + delete bubbleColor; + } + + } + + + +// defined in ScreenGL_SDL.cpp + +// we draw object hint mode differently depending on this +extern char screenGLStencilBufferSupported; + + + +void GameStateDisplay::fireRedraw() { + + // invisible if disabled + if( !mEnabled ) { + return; + } + + + if( ! mHintMode ) { + + double roomTrans = mState->mRoomTrans / 255.0; + + for( int y=0; ydraw( 0, 0, &pos, 1, roomTrans ); + } + + } + } + } + + + if( mHintMode && screenGLStencilBufferSupported ) { + // use stenciling to draw *shadows* of sprites only + + // don't update color + glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // skip fully-transparent areas + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( GL_GREATER, 0 ); + + // Draw 1 into the stencil buffer wherever a sprite is + glEnable( GL_STENCIL_TEST ); + glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE ); + glStencilFunc( GL_ALWAYS, 1, 0xffffffff ); + } + + + // now draw all sprites + int numSprites = mObjectSprites.size(); + int selected = mState->getSelectedObject(); + + if( mLastSelected != selected || ! mShowGrid ) { + // reset + mBlinkCycle = 0; + mLastSelected = selected; + } + else { + mBlinkCycle ++; + } + + double blinkFadeFactor = 0.35 * sin( mBlinkCycle / 3.0 ) + 0.65; + + + //printf( "Drawing %d sprites, %d selected\n", numSprites, selected ); + + for( int i=0; imLocksOn ) { + // anchors on... but for this object? + StateObjectInstance *o = + *( mState->mObjects.getElement( owner ) ); + + if( o->mLocked ) { + drawIt = false; + } + } + + + } + + if( drawIt ) { + char glow = *( mObjectSpriteGlows.getElement( s ) ); + if( glow ) { + // brighten only + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + } + + if( mHintMode ) { + // override fade + fadeFactor = 0.25; + + // black color + c.r = 0; + c.g = 0; + c.b = 0; + } + + + sprite->draw( 0, 0, &pos, 1, fadeFactor, &c ); + + if( glow ) { + // back to normal blend + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + } + } + + if( mHintMode && screenGLStencilBufferSupported ) { + // stencil buffer has 1 wherever sprite is + + // Re-enable update of color + glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + glDisable( GL_ALPHA_TEST ); + + // Now, only render where stencil is set to 1. + glStencilFunc( GL_EQUAL, 1, 0xffffffff ); // draw if == 1 + glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + + + // draw a big dark rectangle, cut out by stenciled areas + + glColor4f( 0, 0, 0, 0.25f ); + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + } + glEnd(); + + + // draw diagonal yellow hash lines cut out by stenciled areas + + glColor4f( 1, 1, 1, 0.25f ); + glBegin( GL_LINES ); { + + for( int x=0; xgetFontColor()->copy(); + largeText->setFontColor( new Color( 0, 0, 0, 1 ) ); + + + if( ! mHintMode ) { + + // draw speech for each object + int numObjects = mState->mObjects.size(); + + // put 0's speech on top + for( int i=1; imObjects.getElement( 0 ) ); + + if( ! mHintMode && + ( strlen( o->mAction ) > 0 + || + ( 0 == selected && mEditingAction ) ) ) { + + intPair pos = ::add( o->mActionOffset, o->mAnchorPosition ); + + double height = 8; + double width = strlen( o->mAction ) * height; + + double trueLength = largeText->measureTextWidth( o->mAction ) * height; + + int totalArrowLength = (int)trueLength + 10; + + + double trueStringHeight = height * + largeText->measureTextHeight( o->mAction ); + + char actionTall = false; + + if( trueStringHeight > 9 ) { + // make room for high characters + actionTall = true; + } + + + + // flip anchor if on left side of player + int flip = 1; + Sprite *endSprite = mActionBoxEndSprite; + + if( o->mActionOffset.x < P/2 ) { + flip = -1; + endSprite = mActionBoxEndFlipSprite; + } + + if( flip == 1 && pos.x - totalArrowLength < 0 ) { + // off left, force flip + flip = -1; + endSprite = mActionBoxEndFlipSprite; + } + else if( flip == -1 && pos.x + totalArrowLength > P * G - 1 ) { + // off right, force flip + flip = 1; + endSprite = mActionBoxEndSprite; + } + + + if( actionTall ) { + if( endSprite == mActionBoxEndSprite ) { + endSprite = mActionBoxEndTallSprite; + } + else { + endSprite = mActionBoxEndFlipTallSprite; + } + } + + + + Vector3D boxPos( mAnchorX + pos.x - flip * 4, + mAnchorY + pos.y + 1, + 0 ); + + if( flip == -1 ) { + // nudge + boxPos.mX += 1; + } + + + double boxFade = 0.63; + + + Vector3D boxStartPos = boxPos; + + endSprite->draw( 0, 0, &boxPos, 1, + boxFade ); + + int trueLengthLeft = (int)trueLength; + + // start can contain... 5 pixels? + //trueLengthLeft -= 7; + + // go a slight bit over to leave extra space at end if it's tight + // end cap adds to it as well + while( trueLengthLeft > 14 ) { + boxPos.mX -= flip * 8; + + if( actionTall ) { + mActionBoxMiddleTallSprite->draw( 0, 0, &boxPos, 1, + boxFade ); + } + else { + mActionBoxMiddleSprite->draw( 0, 0, &boxPos, 1, + boxFade ); + } + + trueLengthLeft -= 8; + } + + // now fill in rest, which is less that 8 pixels, with + // smaller lines + // sprite is 2 columns wide, but one column is transparent + // (drawing 1-column-wide sprites, using centered-based drawing, + // causes round-off errors) + + // adjust a bit, since tall versions are lopsided + if( actionTall && flip < 0 ) { + boxPos.mY += 2; + } + + char firstExtraLine = true; + while( trueLengthLeft > 6 ) { + boxPos.mX -= flip * 1; + if( firstExtraLine ) { + // extra offset from center of last full middle segment + boxPos.mX -= flip * 4; + firstExtraLine = false; + } + + if( actionTall ) { + mActionBoxMiddleExtraTallSprite->draw( 0, 0, &boxPos, flip, + boxFade ); + } + else { + mActionBoxMiddleExtraSprite->draw( 0, 0, &boxPos, flip, + boxFade ); + } + + + trueLengthLeft -= 1; + } + + + // cap with end + + if( !firstExtraLine ) { + // used at least one extra line + boxPos.mX -= flip * 4; + } + else { + // used no extra lines, offst from last full middle segment + boxPos.mX -= flip * 8; + } + + + if( actionTall ) { + mActionBoxStartTallSprite->draw( 0, 0, &boxPos, flip, + boxFade ); + } + else { + mActionBoxStartSprite->draw( 0, 0, &boxPos, flip, + boxFade ); + } + + + if( flip == 1 ) { + boxStartPos = boxPos; + } + else { + // use real start pos, but adjust + boxStartPos.mX += 7; + } + + // make lower to increase chances of it fitting in box + char *lowerAction = stringToLowerCase( o->mAction ); + + largeText->drawText( lowerAction, + boxStartPos.mX - 4 + 1, + boxStartPos.mY - 4, + width, height ); + + delete [] lowerAction; + } + + + + + // restore + largeText->setFontColor( oldFontColor ); + + + + if( mShowGrid && ! mHintMode ) { + //grid lines + + glColor4f( 1, 1, 1, 0.25f ); + glBegin( GL_LINES ); { + + for( int x=0; xdisplayRedrawed(); + } + + } + + + +void GameStateDisplay::setLastMouse( double inX, double inY ) { + mLastPixelClickX = (int)( inX - mAnchorX ); + mLastPixelClickY = (int)( inY - mAnchorY ); + + mLastGridClickX = (int)( mLastPixelClickX / P ); + mLastGridClickY = (int)( mLastPixelClickY / P ); + + mLastHoverGridX = mLastGridClickX; + mLastHoverGridY = mLastGridClickY; + } + + + +void GameStateDisplay::mouseDragged( double inX, double inY ) { + if( mHintMode ) return; + + + mLastActionRelease = false; + mLastActionPress = false; + mLastActionKeyPress = false; + + if( isEnabled() && isInside( inX, inY ) ) { + setLastMouse( inX, inY ); + fireActionPerformed( this ); + } + } + + + +void GameStateDisplay::mousePressed( double inX, double inY ) { + if( mHintMode ) return; + + mLastActionRelease = false; + mLastActionPress = true; + mLastActionKeyPress = false; + + if( isEnabled() && isInside( inX, inY ) ) { + setLastMouse( inX, inY ); + fireActionPerformed( this ); + + // selected object may have changed... update tool tip + mLastMouseoverObjectIndex = -1; + + if( mShowObjectToolTips ) { + redoToolTip( mLastGridClickX, mLastGridClickY ); + } + } + + } + + + +void GameStateDisplay::mouseReleased( double inX, double inY ) { + if( mHintMode ) return; + + mLastActionRelease = true; + mLastActionPress = false; + mLastActionKeyPress = false; + + // done dragging with mouse + mManualBubblePositioningLive = false; + + + if( isEnabled() && isInside( inX, inY ) ) { + setLastMouse( inX, inY ); + fireActionPerformed( this ); + } + } + + + + +#include "ToolTipManager.h" +#include "minorGems/util/TranslationManager.h" + + +void GameStateDisplay::redoToolTip( int inGridX, int inGridY ) { + int objIndex = mState->getHitObject( inGridX, inGridY ); + + if( objIndex != -1 ) { + if( objIndex != mLastMouseoverObjectIndex ) { + + + StateObjectInstance *objInstance = + *( mState->mObjects.getElement( objIndex ) ); + + char *objName = + objInstance->mObject.getStateObjectName(); + + + char *tipString; + + int selected = mState->getSelectedObject(); + + char *selectedString = (char *)""; + if( selected == objIndex ) { + selectedString = + (char*) + TranslationManager::translate( "tip_anchor_selected" ); + } + + + char *transKey; + if( objIndex == 0 ) { + transKey = (char*)"tip_player_anchor"; + } + else { + transKey = (char*)"tip_object_anchor"; + } + + + tipString = autoSprintf( + "%s (%s) %s", + TranslationManager::translate( transKey ), + objName, + selectedString ); + + delete [] objName; + + + ToolTipManager::setTip( tipString ); + delete [] tipString; + } + } + else { + ToolTipManager::setTip( NULL ); + } + + mLastMouseoverObjectIndex = objIndex; + } + + + +void GameStateDisplay::mouseMoved( double inX, double inY ) { + if( mHintMode ) return; + + if( isEnabled() && isInside( inX, inY ) ) { + + int pixelX = (int)( inX - mAnchorX ); + int pixelY = (int)( inY - mAnchorY ); + + int gridX = (int)( pixelX / P ); + int gridY = (int)( pixelY / P ); + + if( mShowObjectToolTips ) { + redoToolTip( gridX, gridY ); + } + + mMouseHover = true; + setLastMouse( inX, inY ); + } + else { + mMouseHover = false; + } + + } + + + +void GameStateDisplay::setFocus( char inFocus ) { + mFocused = inFocus; + } + +char GameStateDisplay::isFocused() { + return mFocused; + } + + +void GameStateDisplay::keyPressed( unsigned char inKey, + double inX, double inY ) { + if( mHintMode ) return; + + mLastActionKeyPress = true; + mLastKeyPressed = inKey; + + if( isEnabled() ) { + fireActionPerformed( this ); + } + } diff --git a/gameSource/GameStateDisplay.h b/gameSource/GameStateDisplay.h new file mode 100644 index 0000000..4f32c7d --- /dev/null +++ b/gameSource/GameStateDisplay.h @@ -0,0 +1,194 @@ +#ifndef GAME_STATE_DISPLAY_INCLUDED +#define GAME_STATE_DISPLAY_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" +#include "minorGems/ui/event/ActionListenerList.h" + + +#include "GameState.h" +#include "common.h" + + + +class GameStateDisplay : public GUIComponentGL, public ActionListenerList { + + public: + + // sets its own width and height + GameStateDisplay( int inAnchorX, int inAnchorY, + char inShowObjectToolTips = false ); + + virtual ~GameStateDisplay(); + + + // only for Controller game + // used to send redraw events back to editor + void setEditor( void *inEditor ); + + + // destroyed by this class + void setState( GameState *inState ); + + // updates sprite positions from data in inState + // inState assumed to be indentical to last setState otherwise + // note that inState is still destroyed by this class! + void updateSpritePositions( GameState *inState ); + + // turns grid/anchor drawing on and off + void showGrid( char inShow ); + + // shows dark, transparent hints for object positions only + // doesn't draw rooms + void setHintMode( char inHintsOnly ); + + // this class auto-adjusts speech flips and offsets + // for display purposes + // this function allows the new ones to be copied into another + // game state + // inDestinationState must have the same number of objects, etc + void copySpeechCoordinates( GameState *inDestinationState ); + + // if true, then above function should be called + char mHasAnySpeechBeenAutoFlipped; + + // set to true if currently in the middle of manual bubble positioning + // with mouse + // this is auto-set to false whenever mouse released + char mManualBubblePositioningLive; + + char getSpeechOffTop( int inObject ); + + + + + // override + virtual void fireRedraw(); + virtual void mouseDragged( double inX, double inY ); + virtual void mousePressed( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + virtual void mouseMoved( double inX, double inY ); + + virtual void setFocus( char inFocus ); + virtual char isFocused(); + virtual void keyPressed( unsigned char inKey, + double inX, double inY ); + + // after this display fires an action, find out + // where the mouse event happened + int mLastPixelClickX, mLastPixelClickY; + int mLastGridClickX, mLastGridClickY; + + char mLastActionRelease; + char mLastActionPress; + char mLastActionKeyPress; + + unsigned char mLastKeyPressed; + + + int mLastMouseoverObjectIndex; + + char mMouseHover; + int mLastHoverGridX, mLastHoverGridY; + + char mEditingSpeech; + char mEditingAction; + + + char mHighlightEditedBubble; + + + protected: + + char mShowObjectToolTips; + + + void setLastMouse( double inX, double inY ); + void redoToolTip( int inGridX, int inGridY ); + + + void *mEditor; + + + char mFocused; + + char mShowGrid; + + char mHintMode; + + + GameState *mState; + + Sprite *mSprites[G][G]; + + + SimpleVector mSpeechOffTop; + + + SimpleVector mObjectSprites; + SimpleVector mObjectSpritePositions; + SimpleVector mObjectSpriteOwners; + SimpleVector mObjectSpriteTrans; + SimpleVector mObjectSpriteGlows; + + // depth from 0 to G (where G is above everything else) + // allows quick sorting into horizontal depth bands + SimpleVector mObjectSpriteDepth; + // we draw locked ones behind others at same depth + SimpleVector mObjectSpriteLocked; + + + // maps i=[0,numSprites) to indices in depth-draw order + SimpleVector mDrawOrderMap; + + void recomputeDrawOrderMap(); + + + + + Sprite *mBubbleTopSprite; + Sprite *mBubbleMiddleSprite; + Sprite *mBubbleMiddleExtraSprite; + Sprite *mBubbleMiddleExtraThinSprite; + Sprite *mBubbleBottomSprite; + Sprite *mBubbleBottomSpriteFlip; + Sprite *mBubbleBottomNoTailSprite; + Sprite *mBubbleMiddleTailSprite; + Sprite *mBubbleMiddleTailFlipSprite; + + Sprite *mSpeechBoxTopSprite; + Sprite *mSpeechBoxBottomSprite; + + + Sprite *mActionBoxStartSprite; + Sprite *mActionBoxMiddleSprite; + Sprite *mActionBoxMiddleExtraSprite; + Sprite *mActionBoxEndSprite; + Sprite *mActionBoxEndFlipSprite; + + Sprite *mActionBoxStartTallSprite; + Sprite *mActionBoxMiddleTallSprite; + Sprite *mActionBoxMiddleExtraTallSprite; + Sprite *mActionBoxEndTallSprite; + Sprite *mActionBoxEndFlipTallSprite; + + + // for blinking selected anchor + int mLastSelected; + int mBlinkCycle; + + + private: + + void addSpritesFromStateInstance( int inIndex ); + + // returns new sprite index + int updatePositionFromStateInstance( int inIndex, int inSpriteIndex ); + + + + void drawSpeech( int inIndex ); + + }; + + +#endif diff --git a/gameSource/GameStateEditor.cpp b/gameSource/GameStateEditor.cpp new file mode 100644 index 0000000..1d22bc1 --- /dev/null +++ b/gameSource/GameStateEditor.cpp @@ -0,0 +1,1876 @@ +#include "GameStateEditor.h" +#include "RoomPicker.h" +#include "RoomEditor.h" +#include "ScenePicker.h" +#include "TilePicker.h" +#include "StateObjectPicker.h" +#include "StateObjectEditor.h" +#include "DragAndDropManager.h" +#include "BorderPanel.h" +#include "labels.h" +#include "SpriteResource.h" +#include "packSaver.h" +#include "ToolTipManager.h" + + +#include "minorGems/util/TranslationManager.h" + +#include "minorGems/util/log/AppLog.h" + + +#include + + +extern StateObjectPicker *mainStateObjectPicker; +extern RoomPicker *mainRoomPicker; +extern RoomEditor *mainRoomEditor; +extern ScenePicker *mainScenePicker; +extern TilePicker *mainTilePicker; + +extern StateObjectEditor *mainStateObjectEditor; + +extern DragAndDropManager *mainDragAndDrop; + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + +template <> +void SizeLimitedVector::deleteElementOfType( + GameState *inElement ) { + // special delete needed, because vector stores pointers + delete inElement; + } + + + +GameStateEditor::GameStateEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mEditingSelectedObject( false ), + mUndoStack( MAX_UNDOS, true ), // stack contains pointers + mIgnoreObjPickerEvents( false ), + mObjectChanging( false ), + // gen a fake ID + mCurrentWorkingStateID( makeUniqueID( (unsigned char*)"currState", + strlen( "currState" ) ) ) { + + AppLog::info( "Constructing game state editor\n" ); + + /* + LabelGL *titleLabel = new LabelGL( 0.75, 0.5, 0.25, + 0.1, "Room Editor", largeText ); + + + + mSidePanel->add( titleLabel ); + */ + + // hide the close button for this editor, since it's the base editor + mCloseButton->setEnabled( false ); + + mSidePanel->add( mainStateObjectPicker ); + + mainStateObjectPicker->addActionListener( this ); + + + mSidePanel->add( mainScenePicker ); + + mainScenePicker->addActionListener( this ); + + + // listen for room changes too + mainRoomPicker->addActionListener( this ); + + + + mStateDisplay = new GameStateDisplay( 8, gameWidth - 48 - G * P, + true ); + mMainPanel->add( mStateDisplay ); + mStateDisplay->addActionListener( this ); + + // turn on active bubble coloring + mStateDisplay->mHighlightEditedBubble = true; + + mStateDisplay->setEditor( this ); + + + mainRoomEditor->setGameStateDisplay( mStateDisplay ); + + + + mEditStateObjectButton = + new EditButtonGL( + mainStateObjectPicker->getAnchorX() - 1, + mainStateObjectPicker->getAnchorY() + + mainStateObjectPicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mEditStateObjectButton ); + + mEditStateObjectButton->addActionListener( this ); + mEditStateObjectButton->setToolTip( "tip_edit_object" ); + + + + double offset = P; + + double buttonSize = P; + + + + + mToolSet = new StateToolSet( 26, 42 ); + mMainPanel->add( mToolSet ); + mToolSet->addActionListener( this ); + + + + mSpeechDeleteButton = new QuickDeleteButtonGL( + mToolSet->getAnchorX() + mToolSet->getWidth(), + mToolSet->getAnchorY() + mToolSet->getHeight() - 8, + 8, + 8 ); + mMainPanel->add( mSpeechDeleteButton ); + mSpeechDeleteButton->addActionListener( this ); + + mSpeechDeleteButton->setEnabled( false ); + + mSpeechDeleteButton->setToolTip( "tip_delete_speech" ); + + + + mSpeechBubbleBoxButton = new ToggleSpriteButtonGL( + new Sprite( "speechBox.tga", true ), + new Sprite( "speechBubble.tga", true ), + 1, + mToolSet->getAnchorX() + mToolSet->getWidth(), + mToolSet->getAnchorY(), + 8, + 8 ); + mMainPanel->add( mSpeechBubbleBoxButton ); + mSpeechBubbleBoxButton->addActionListener( this ); + + mSpeechBubbleBoxButton->setEnabled( false ); + + mSpeechBubbleBoxButton->setToolTip( "tip_toggle_box" ); + mSpeechBubbleBoxButton->setSecondToolTip( "tip_toggle_bubble" ); + + + + + mFlipSpeechButton = + new FlipHButtonGL( mSpeechDeleteButton->getAnchorX() + + mSpeechDeleteButton->getWidth() + 1, + 44, + 16, 16 ); + mMainPanel->add( mFlipSpeechButton ); + mFlipSpeechButton->addActionListener( this ); + + mFlipSpeechButton->setToolTip( "tip_flip_speech" ); + mFlipSpeechButton->setEnabled( false ); + + + + + + + + + + + + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "sceneName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( false ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + // not undoable, ignore actions (so that we can hold + // focus on GameStateDisplay whenever we can) + //mNameField->addActionListener( this ); + + + + + + + // center side buttons + double gridEdge = 8 + G * buttonSize; + + double extra = gameHeight - gridEdge; + + + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + + + + + double undoButtonY = gameWidth - ( 48 + offset + (G - 1) * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + mUndoOrRedoOrClearAction = false; + mIgnoreSliders = false; + + + + + + + + + + + double setY = mainStateObjectPicker->getAnchorY() + + mainStateObjectPicker->getHeight() - 15; + + mSetObjectButton = new SpriteButtonGL( + new Sprite( "setObject.tga", true ), + 1, + sideButtonsX, setY, 16, 16 ); + + mMainPanel->add( mSetObjectButton ); + + mSetObjectButton->addActionListener( this ); + + mSetObjectButton->setToolTip( "tip_objSet" ); + + + + mAddObjectButton = new KeyEquivButtonGL( + new Sprite( "addObject.tga", true ), + 1, + sideButtonsX, setY - 20, 16, 16, 'a', 'A' ); + + mMainPanel->add( mAddObjectButton ); + + mAddObjectButton->addActionListener( this ); + + mAddObjectButton->setToolTip( "tip_objAdd" ); + + + + mEditObjectButton = new KeyEquivButtonGL( + new Sprite( "editObject.tga", true ), + 1, + sideButtonsX, setY - 40, 16, 16, 'e', 'E' ); + + mMainPanel->add( mEditObjectButton ); + + mEditObjectButton->addActionListener( this ); + + mEditObjectButton->setToolTip( "tip_objEdit" ); + + + mRemoveObjectButton = new KeyEquivButtonGL( + new Sprite( "removeObject.tga", true ), + 1, + sideButtonsX, setY - 60, 16, 16, 'x', 'X' ); + + mMainPanel->add( mRemoveObjectButton ); + + mRemoveObjectButton->addActionListener( this ); + + mRemoveObjectButton->setToolTip( "tip_objRemove" ); + + // obj 0 starts out selected, can't be deleted + mRemoveObjectButton->setEnabled( false ); + + + + // freeze only works on obj 0, which can't be removed + // so re-use the space + // actually, reusing space causes cross-events on click--- UGH + // Also probably BAD UI design... + mFreezeButton = new SelectableButtonGL( + new Sprite( "freeze.tga", true ), + 1, + 104, 42, 20, 20 ); + + mMainPanel->add( mFreezeButton ); + + mFreezeButton->setSelected( false ); + + mFreezeButton->addActionListener( this ); + + mFreezeButton->setToolTip( "tip_freezePlayer" ); + + + + mLockSelectedButton = new SelectableButtonGL( + new Sprite( "lockSelected.tga", true ), + 1, + 104, 42, 20, 20 ); + + mMainPanel->add( mLockSelectedButton ); + + mLockSelectedButton->setSelected( false ); + + mLockSelectedButton->addActionListener( this ); + + mLockSelectedButton->setEnabled( false ); + + mLockSelectedButton->setToolTip( "tip_lockSelected" ); + + + + mLockGlobalButton = new SelectableButtonGL( + new Sprite( "lock.tga", true ), + 1, + 126, 42, 20, 20 ); + + mMainPanel->add( mLockGlobalButton ); + + mLockGlobalButton->setSelected( true ); + + mLockGlobalButton->addActionListener( this ); + + mLockGlobalButton->setEnabled( true ); + + mLockGlobalButton->setToolTip( "tip_lockGlobal" ); + + + + mHoldObjectButton = new SelectableButtonGL( + new Sprite( "hold.tga", true ), + 1, + mLockSelectedButton->getAnchorX() - 13, + mLockSelectedButton->getAnchorY() + 2, + 12, 12 ); + + + mHoldObjectButton->setSelected( false ); + + mHoldObjectButton->setToolTip( "tip_holdObject" ); + + mMainPanel->add( mHoldObjectButton ); + + mHoldObjectButton->addActionListener( this ); + + + + Color *thumbColor = new Color( .5, .5, .5, .5 ); + Color *borderColor = new Color( .35, .35, .35, .35 ); + + + mObjectTransSlider = new ToolTipSliderGL( sideButtonsX - 1, setY - 80 + 9, + 18, 10, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mObjectTransSlider ); + mObjectTransSlider->setThumbPosition( 1.0 ); + mObjectTransSlider->addActionListener( this ); + mObjectTransSlider->setToolTip( "tip_object_fade" ); + + + + + mGridButton = new SelectableButtonGL( + new Sprite( "grid.tga", true ), + 1, + sideButtonsX - 2, 158, 20, 20 ); + + mMainPanel->add( mGridButton ); + + mGridButton->setSelected( true ); + + mGridButton->addActionListener( this ); + + mGridButton->setToolTip( "tip_grid" ); + + + + mRoomTransSlider = new ToolTipSliderGL( sideButtonsX - 1, setY - 120 + 13, + 18, 10, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mRoomTransSlider ); + mRoomTransSlider->setThumbPosition( 1.0 ); + mRoomTransSlider->addActionListener( this ); + mRoomTransSlider->setToolTip( "tip_room_fade" ); + + + + + + + + + + + // center it vertically on scene picker + double addSceneY = mainScenePicker->getAnchorY() + + mainScenePicker->getHeight() - 15; + + + mAddSceneButton = new AddButtonGL( sideButtonsX, + addSceneY, + 16, 16 ); + mMainPanel->add( mAddSceneButton ); + mAddSceneButton->addActionListener( this ); + mAddSceneButton->setToolTip( "tip_addScene" ); + + mAddSceneAction = false; + + double clearY = addSceneY - 20; + + mClearButton = new ClearButtonGL( sideButtonsX, clearY, 16, 16 ); + mMainPanel->add( mClearButton ); + mClearButton->addActionListener( this ); + + + + mSendButton = new SendButtonGL( + 4, + // below undo button + 44, + 16, 16 ); + + mMainPanel->add( mSendButton ); + + mSendButton->addActionListener( this ); + + mSendButton->setToolTip( "tip_send" ); + + + + mPracticeButton = new SpriteButtonGL( + new Sprite( "practice.tga", true ), + 1, + 4, + // below undo button + 44, + 16, 16 ); + + mMainPanel->add( mPracticeButton ); + + mPracticeButton->addActionListener( this ); + + mPracticeButton->setToolTip( "tip_practice" ); + mPracticeButton->setEnabled( false ); + + + // music button in side panel + double musicButtonY = + mainScenePicker->getAnchorY() + mainScenePicker->getHeight() + + (int)( mainStateObjectPicker->getAnchorY() - + ( mainScenePicker->getAnchorY() + mainScenePicker->getHeight() ) + - 16 ) / 2; + + + mEditMusicButton = new SpriteButtonGL( + new Sprite( "music.tga", true ), + 1, + mSidePanel->getAnchorX() + ( mSidePanel->getWidth() - 16 ) / 2, + musicButtonY, 16, 16 ); + + mSidePanel->add( mEditMusicButton ); + + mEditMusicButton->addActionListener( this ); + + mEditMusicButton->setToolTip( "tip_edit_music" ); + + + + + mEditRoomButton = new SpriteButtonGL( + new Sprite( "editRoom.tga", true ), + 1, + mainScenePicker->getAnchorX() - 1, musicButtonY, 16, 16 ); + + mSidePanel->add( mEditRoomButton ); + + mEditRoomButton->addActionListener( this ); + + mEditRoomButton->setToolTip( "tip_edit_room" ); + + + + + + + delete thumbColor; + delete borderColor; + + + + + mEditStateObjectButton = + new EditButtonGL( + mainStateObjectPicker->getAnchorX() - 1, + mainStateObjectPicker->getAnchorY() + + mainStateObjectPicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mEditStateObjectButton ); + + mEditStateObjectButton->addActionListener( this ); + mEditStateObjectButton->setToolTip( "tip_edit_object" ); + + + + mAddToPackButton = new ToggleSpriteButtonGL( + new Sprite( "pack.tga", true ), + new Sprite( "packAlreadyIn.tga", true ), + 1, + mainScenePicker->getAnchorX() + mainScenePicker->getWidth() - 22, + mainScenePicker->getAnchorY() + + mainScenePicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mAddToPackButton ); + + mAddToPackButton->addActionListener( this ); + + mAddToPackButton->setToolTip( "tip_addSceneToPack" ); + mAddToPackButton->setSecondToolTip( "tip_sceneAlreadyInPack" ); + + + mSavePackButton = new SpriteButtonGL( + new Sprite( "packSave.tga", true ), + 1, + mainScenePicker->getAnchorX() + mainScenePicker->getWidth() - 7, + mainScenePicker->getAnchorY() + + mainScenePicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mSavePackButton ); + + mSavePackButton->addActionListener( this ); + + mSavePackButton->setToolTip( "tip_savePack" ); + + + + + + mGameStateToEdit = NULL; + + GameState *state = new GameState(); + + setGameStateToEdit( state ); + + + + mNoDropImage = readTGA( "noDrop.tga" ); + mCanDropImage = readTGA( "canDrop.tga" ); + + + + // listen for clicks on main panel so we can re-focus for speech input + mMainPanel->addActionListener( this ); + + + // dnd on top of side panel + postConstructionSide(); + + mMainPanel->add( mainDragAndDrop ); + + // but *below* special main panel widgets + postConstructionMain(); + } + + + +GameStateEditor::~GameStateEditor() { + mSidePanel->remove( mainStateObjectPicker ); + mSidePanel->remove( mainScenePicker ); + + mMainPanel->remove( mainDragAndDrop ); + + if( mGameStateToEdit != NULL ) { + delete mGameStateToEdit; + } + + int numInStack = mUndoStack.size(); + int i; + for( i=0; igetSelectedObject(); + + + StateObjectInstance *inst = + *( mGameStateToEdit-> + mObjects.getElement( selectedObj ) ); + + + mainStateObjectPicker->setSelectedResource( + inst->mObject ); + + + + mainRoomPicker->setSelectedResource( mGameStateToEdit->mRoom ); + } + + + + mStateDisplay->setState( mGameStateToEdit->copy() ); + + + + // make sure speech toggles match current state (in case of undo/redo) + mStateDisplay->mEditingSpeech = + ( mToolSet->getSelected() == speak ); + + // always keep focus on display... + //mStateDisplay->setFocus( mStateDisplay->mEditingSpeech ); + + // show delete button whenever non-empty speech object selected, + // even if not currently in Speech mode + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + + if( mGameStateToEdit->getSelectedObject() > 0 ) { + mRemoveObjectButton->setEnabled( true ); + } + else { + // never delete object 0 (player object) + mRemoveObjectButton->setEnabled( false ); + } + + + mFreezeButton->setSelected( mGameStateToEdit->isObjectZeroFrozen() ); + + mFreezeButton->setEnabled( mGameStateToEdit->getSelectedObject() == 0 ); + + int selectedObjectIndex = mGameStateToEdit->getSelectedObject(); + + mLockSelectedButton->setEnabled( selectedObjectIndex != 0 ); + + mLockSelectedButton->setSelected( + mGameStateToEdit->getSelectedLocked() ); + + mLockGlobalButton->setSelected( + mGameStateToEdit->mLocksOn ); + + mHoldObjectButton->setSelected( + mGameStateToEdit->getObjectHeld( selectedObjectIndex ) ); + + + checkFlipAndDeleteSpeechButtonEnabled(); + + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + + mIgnoreSliders = true; + + // only 36 pixels in slider... avoid round-off errors + mRoomTransSlider->setThumbPosition( + (int)( mGameStateToEdit->mRoomTrans / 255.0 * 36 ) / 36.0 ); + + mObjectTransSlider->setThumbPosition( + (int)( mGameStateToEdit->getSelectedTrans() / 255.0 * 36 ) / 36.0 ); + + mIgnoreSliders = false; + + mAddObjectButton->setEnabled( mGameStateToEdit->canAdd() ); + + getTileUnder(); + + + // add usages for current working state + int numObjects = mGameStateToEdit->mObjects.size(); + for( int i=0; imObjects.getElement( i ) ); + addUsage( mCurrentWorkingStateID, obj->mObject.getUniqueID() ); + } + mainStateObjectPicker->recheckDeletable(); + } + + + +#include "Connection.h" + + +extern Connection *connection; + +extern char practiceMode; + + +void GameStateEditor::enableSend( char inEnabled ) { + mSendButton->setEnabled( inEnabled ); + + mPracticeButton->setEnabled( false ); + + if( !inEnabled ) { + // show practice button? + + if( !practiceMode && + ( connection == NULL || ! connection->isConnected() ) ) { + + mPracticeButton->setEnabled( true ); + } + } + + } + + + + +void GameStateEditor::aboutToSend() { + mSendButton->setEnabled( false ); + //mSendButton->resetPressState(); + + // clear player's actions/speech + StateObjectInstance *objZero = + *( mGameStateToEdit->mObjects.getElement( 0 ) ); + + delete [] objZero->mSpokenMessage; + objZero->mSpokenMessage = stringDuplicate( "" ); + + objZero->mAction[0] = '\0'; + + // re-center action (now empty) on player + mGameStateToEdit->resetActionAnchor( 0 ); + + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + + + + +void GameStateEditor::clearRedoStack() { + int numInStack = mRedoStack.size(); + for( int i=0; isetEnabled( false ); + } + + +void GameStateEditor::saveUndoPoint() { + mUndoStack.push_back( mGameStateToEdit->copy() ); + mUndoButton->setEnabled( true ); + + // new branch, old redo future impossible + clearRedoStack(); + } + + + +void GameStateEditor::checkFlipAndDeleteSpeechButtonEnabled() { + int speechLength = mGameStateToEdit->getSelectedSpeechLength(); + + // show delete button whenever there is speech, even if not + // in delete mode. + if( speechLength > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + int selObject = mGameStateToEdit->getSelectedObject(); + + StateObjectInstance *o = + *( mGameStateToEdit->mObjects.getElement( selObject ) ); + + mSpeechBubbleBoxButton->setState( o->mSpeechBox ); + + + if( mStateDisplay->mEditingSpeech || speechLength > 0 ) { + + // speech visible, always allow to switch to a box + mSpeechBubbleBoxButton->setEnabled( true ); + + + + int bubbleWidth = 64; + + intPair bubblePos = ::add( o->mSpeechOffset, o->mAnchorPosition ); + + char offTop = mStateDisplay->getSpeechOffTop( selObject ); + + if( ! o->mSpeechBox && offTop ) { + bubbleWidth += 10; + } + + int autoOffsetIfFlipped = + mGameStateToEdit->getAutoOffsetOnFlip( selObject ); + + if( !o->mSpeechFlip && + bubblePos.x + autoOffsetIfFlipped - bubbleWidth < 0 ) { + // would be off left edge if flipped + mFlipSpeechButton->setEnabled( false ); + } + else if( o->mSpeechFlip && + bubblePos.x + autoOffsetIfFlipped + bubbleWidth > + P * G ) { + // would be off right edge if flipped + mFlipSpeechButton->setEnabled( false ); + } + else { + mFlipSpeechButton->setEnabled( true ); + } + } + else { + // else speech bubble not even visible + mFlipSpeechButton->setEnabled( false ); + mSpeechBubbleBoxButton->setEnabled( false ); + } + } + + + + +void GameStateEditor::displayRedrawed() { + + // don't wait for action to detect an auto-flip + if( mStateDisplay->mHasAnySpeechBeenAutoFlipped ) { + // make sure we get speech pos/flip tweaks added for display purposes + mStateDisplay->copySpeechCoordinates( mGameStateToEdit ); + + // got it! + mStateDisplay->mHasAnySpeechBeenAutoFlipped = false; + + checkFlipAndDeleteSpeechButtonEnabled(); + } + } + + + +void GameStateEditor::setSelectedObject( int inIndex ) { + + int objIndex = inIndex; + + mGameStateToEdit->setSelectedObject( objIndex ); + + mIgnoreSliders = true; + + mObjectTransSlider->setThumbPosition( + (int)( mGameStateToEdit->getSelectedTrans() / + 255.0 * 36 ) + / 36.0 ); + + mIgnoreSliders = false; + + + // ignore change events that we cause + mIgnoreObjPickerEvents = true; + + // no need to generate all new sprites---it's slow! + //setGameStateToEdit( mGameStateToEdit->copy() ); + + mFreezeButton->setEnabled( objIndex == 0 ); + mLockSelectedButton->setEnabled( objIndex != 0 ); + + mLockSelectedButton->setSelected( + mGameStateToEdit->getSelectedLocked() ); + + mHoldObjectButton->setSelected( + mGameStateToEdit->getObjectHeld( objIndex ) ); + + + // switch obj picker + StateObjectInstance *inst = + *( mGameStateToEdit-> + mObjects.getElement( objIndex ) ); + + mainStateObjectPicker->setSelectedResource( + inst->mObject ); + + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + + checkFlipAndDeleteSpeechButtonEnabled(); + + mIgnoreObjPickerEvents = false; + + if( objIndex > 0 ) { + // deleteable + mRemoveObjectButton->setEnabled( true ); + } + else { + mRemoveObjectButton->setEnabled( false ); + } + + getTileUnder(); + } + + + +void GameStateEditor::getTileUnder() { + StateObjectInstance *o = + *( mGameStateToEdit->mObjects.getElement( + mGameStateToEdit->getSelectedObject() ) ); + + uniqueID hitTileID = mGameStateToEdit->mRoom.getTile( + o->mAnchorPosition.x / P, + G - o->mAnchorPosition.y / P - 1 ); + + Tile hitTile( hitTileID ); + + mainTilePicker->setBackgroundTile( hitTile ); + } + + + + +void GameStateEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainStateObjectPicker ) { + if( mainStateObjectPicker->wasLastActionFromPress() && + this->isVisible() ) { + + StateObject resource = + mainStateObjectPicker->getDraggedResource(); + + int numSprites = resource.getNumLayers(); + + + int numTotalSprites = numSprites + 1; + + intPair *offsets = new intPair[ numTotalSprites ]; + Sprite **sprites = new Sprite*[ numTotalSprites ]; + float *trans = new float[ numTotalSprites ]; + char *glows = new char[ numTotalSprites ]; + + for( int i=0; icanAdd() ) { + // buster on top + topSprite = new Sprite( mNoDropImage, true ); + ToolTipManager::setTip( + (char*)TranslationManager::translate( "tip_sceneFull" ) ); + } + else { + // nothing on top + topSprite = new Sprite( mCanDropImage, true ); + + ToolTipManager::setTip( + (char*)TranslationManager::translate( + "tip_draggingObjectScene" ) ); + } + ToolTipManager::freeze( true ); + + offsets[numSprites].x = 0; + offsets[numSprites].y = 0; + trans[numSprites] = 0.5f; + glows[numSprites] = false; + sprites[numSprites] = topSprite; + + + mainDragAndDrop->setSprites( numTotalSprites, sprites, offsets, + trans, glows, 1 ); + + + + + /* + int numSprites = resource.getNumLayers(); + intPair *offsets = new intPair[ numSprites ]; + Sprite **sprites = new Sprite*[ numSprites ]; + float *trans = new float[ numSprites ]; + + for( int i=0; isetSprites( numSprites, sprites, offsets, + trans, 1 ); + */ + } + else if( ! mIgnoreObjPickerEvents && ! mUndoOrRedoOrClearAction && + ! mainStateObjectPicker->wasLastActionFromPress() ) { + + // new obj picked through user picker action, + // replace selected object + + // changed: don't do anything here + // wait for SetObjectButton + + // except if selected object is default... then replace it + + StateObjectInstance *objInstance = + *( mGameStateToEdit->mObjects.getElement( + mGameStateToEdit->getSelectedObject() ) ); + + if( ( mEditingSelectedObject && + mainStateObjectEditor->isVisible() ) + || + equal( objInstance->mObject.getUniqueID(), + StateObject::sBlankObject->getUniqueID() ) ) { + + // we're editing the selected one + // OR + // selected object is blank (so we obviously should change it) + + + + // replace automatically with newly-picked object + + saveUndoPoint(); + + mGameStateToEdit->changeSelectedObject( + mainStateObjectPicker->getSelectedResource() ); + + // generate new sprites + setGameStateToEdit( + mGameStateToEdit->copy() ); + } + } + } + else if( inTarget == mainRoomPicker ) { + // ignore if this room change was triggered by our Undo/Redo + if( !mUndoOrRedoOrClearAction && + ! mainRoomPicker->wasLastActionFromPress() ) { + + // will change room + + saveUndoPoint(); + + mGameStateToEdit->mRoom = mainRoomPicker->getSelectedResource(); + + mStateDisplay->setState( mGameStateToEdit->copy() ); + + getTileUnder(); + // don't pass through this, because it changes picker again + // setGameStateToEdit( mGameStateToEdit->copy() ); + } + } + else if( inTarget == mainScenePicker ) { + + Scene scenePicked = mainScenePicker->getSelectedResource(); + + // ignore if this scene change was triggered by an add + if( !mAddSceneAction && + ! mainScenePicker->wasLastActionFromPress() ) { + + // will change everything + saveUndoPoint(); + + // block picker changes from firing + mUndoOrRedoOrClearAction = true; + + + + GameState *loaded = new GameState(); + + char *sceneName = scenePicked.getSceneName(); + + mNameField->setText( sceneName ); + + delete [] sceneName; + + // keep global lock button state intact + loaded->mLocksOn = mGameStateToEdit->mLocksOn; + + Room loadedRoom( scenePicked.mRoom ); + + loaded->mRoom = loadedRoom; + loaded->mRoomTrans = scenePicked.mRoomTrans; + + loaded->mObjectZeroFrozen = scenePicked.mObjectZeroFrozen; + + + int numObj = scenePicked.mObjects.size(); + + for( int i=0; igetObjectHeld( 0 ) ) { + + // copy object 0 over, ignoring obj 0 in scene + + obj = ( *( mGameStateToEdit->mObjects.getElement( 0 ) ) )-> + mObject; + held = true; + } + + + intPair pos = + *( scenePicked.mObjectOffsets.getElement( i ) ); + + if( i == 0 ) { + // 0 already there in empty state... replace it + loaded->changeSelectedObject( obj ); + loaded->moveSelectedObject( pos.x / P, pos.y / P ); + } + else { + // add new, it will be selected + loaded->newObject( pos.x / P, pos.y / P, obj ); + } + + intPair speechOffset = + *( scenePicked.mSpeechOffsets.getElement( i ) ); + intPair speechPos = add( speechOffset, pos ); + + loaded->moveSelectedSpeechAnchor( speechPos.x, speechPos.y ); + + if( *( scenePicked.mSpeechFlipFlags.getElement( i ) ) ) { + loaded->flipSelectedSpeech(); + } + + loaded->setSelectedSpeechBox( + *( scenePicked.mSpeechBoxFlags.getElement( i ) ) ); + + loaded->setSelectedLocked( + *( scenePicked.mLockedFlags.getElement( i ) ) ); + + loaded->adjustSelectedTrans( + *( scenePicked.mObjectTrans.getElement( i ) ) ); + + if( held ) { + // leave held status on for held objects across + // scene changes + loaded->setObjectHeld( i, true ); + } + } + + // finally, any non-player objects held are added ON TOP of the + // scene (in addition to all scene objects + + int numOldObjects = mGameStateToEdit->mObjects.size(); + for( int i=1; imObjects.getElement( i ) ); + + if( objInst->mHeld ) { + loaded->mObjects.push_back( objInst->copy() ); + } + } + + + // always switch to player object after loading a scene + loaded->setSelectedObject( 0 ); + + setGameStateToEdit( loaded ); + + mainStateObjectPicker->recheckDeletable(); + + mUndoOrRedoOrClearAction = false; + } + + mAddToPackButton->setState( + alreadyInPack( scenePicked.getUniqueID() ) ); + } + else if( inTarget == mAddSceneButton ) { + mAddSceneAction = true; + + Scene sceneToSave; + + sceneToSave.editSceneName( mNameField->getText() ); + + // pack game state into this scene + sceneToSave.mRoom = mGameStateToEdit->mRoom.getUniqueID(); + + sceneToSave.mRoomTrans = mGameStateToEdit->mRoomTrans; + + sceneToSave.mObjectZeroFrozen = + mGameStateToEdit->mObjectZeroFrozen; + + int numObj = mGameStateToEdit->mObjects.size(); + + for( int i=0; imObjects.getElement( i ) ); + + sceneToSave.mObjects.push_back( + objInstance->mObject.getUniqueID() ); + + sceneToSave.mObjectOffsets.push_back( + objInstance->mAnchorPosition ); + + sceneToSave.mSpeechOffsets.push_back( + objInstance->mSpeechOffset ); + + sceneToSave.mSpeechFlipFlags.push_back( + objInstance->mSpeechFlip ); + + sceneToSave.mSpeechBoxFlags.push_back( + objInstance->mSpeechBox ); + + sceneToSave.mLockedFlags.push_back( + objInstance->mLocked ); + + sceneToSave.mObjectTrans.push_back( + objInstance->mObjectTrans ); + } + + + sceneToSave.finishEdit(); + mainScenePicker->setSelectedResource( sceneToSave, true ); + mainStateObjectPicker->recheckDeletable(); + + mAddSceneAction = false; + } + else if( inTarget == mSetObjectButton ) { + saveUndoPoint(); + + mGameStateToEdit->changeSelectedObject( + mainStateObjectPicker->getSelectedResource() ); + + // generate new sprites + setGameStateToEdit( + mGameStateToEdit->copy() ); + } + else if( inTarget == mAddObjectButton ) { + saveUndoPoint(); + + StateObject resource = + mainStateObjectPicker->getDraggedResource(); + + if( mStateDisplay->mMouseHover ) { + // mouse over grid + // add new object at mouse + mGameStateToEdit->newObject( + mStateDisplay->mLastHoverGridX, + mStateDisplay->mLastHoverGridY, + resource ); + } + else { + + // center + mGameStateToEdit->newObject( + G / 2, + G / 2, + resource ); + } + + // generate new sprites + setGameStateToEdit( mGameStateToEdit->copy() ); + } + else if( inTarget == mEditObjectButton ) { + // editing selected... auto-replace + mEditingSelectedObject = true; + + // make sure selected one is chosen in picker + mIgnoreObjPickerEvents = true; + + StateObjectInstance *inst = + *( mGameStateToEdit-> + mObjects.getElement( + mGameStateToEdit->getSelectedObject() ) ); + + mainStateObjectPicker->setSelectedResource( + inst->mObject ); + mIgnoreObjPickerEvents = false; + + showStateObjectEditor(); + } + else if( inTarget == mRemoveObjectButton ) { + saveUndoPoint(); + mGameStateToEdit->deleteSelectedObject(); + + if( mGameStateToEdit->getSelectedObject() > 0 ) { + mRemoveObjectButton->setEnabled( true ); + } + else { + // never delete object 0 (player object) + mRemoveObjectButton->setEnabled( false ); + } + + setGameStateToEdit( mGameStateToEdit->copy() ); + } + else if( inTarget == mSendButton ) { + mSendButton->setEnabled( false ); + + fireActionPerformed( this ); + } + else if( inTarget == mPracticeButton ) { + mPracticeButton->setEnabled( false ); + practiceMode = true; + enableSend( true ); + } + else if( inTarget == mEditStateObjectButton ) { + // edit whatever is in picker + mEditingSelectedObject = false; + showStateObjectEditor(); + } + else if( inTarget == mEditRoomButton ) { + mStateDisplay->setHintMode( true ); + mainRoomEditor->takeOverGameStateDisplay(); + showRoomEditor(); + } + else if( inTarget == mEditMusicButton ) { + showSongEditor(); + } + else if( inTarget == mAddToPackButton ) { + Scene sceneToLoad = mainScenePicker->getSelectedResource(); + + AppLog::info( "Adding a scene to the current resource pack" ); + sceneToLoad.saveToPack(); + + mAddToPackButton->setState( true ); + } + else if( inTarget == mSavePackButton ) { + AppLog::info( "Saving the current resource pack" ); + savePack(); + + mAddToPackButton->setState( false ); + } + else if( inTarget == mUndoButton ) { + mUndoOrRedoOrClearAction = true; + + int lastIndex = mUndoStack.size() - 1; + + GameState *last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mGameStateToEdit->copy() ); + mRedoButton->setEnabled( true ); + + setGameStateToEdit( last ); + + mUndoOrRedoOrClearAction = false; + } + else if( inTarget == mRedoButton ) { + mUndoOrRedoOrClearAction = true; + + int nextIndex = mRedoStack.size() - 1; + + GameState *next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mGameStateToEdit->copy() ); + mUndoButton->setEnabled( true ); + + setGameStateToEdit( next ); + + mUndoOrRedoOrClearAction = false; + } + else if( inTarget == mToolSet ) { + // tool change? + + // we only care about one: + // if speech tool, switch keyboard to main display + + mStateDisplay->mEditingSpeech = + ( mToolSet->getSelected() == speak ); + + checkFlipAndDeleteSpeechButtonEnabled(); + } + else if( inTarget == mSpeechDeleteButton ) { + saveUndoPoint(); + mGameStateToEdit->deleteAllCharsFromSelectedSpeech(); + + if( mGameStateToEdit->getSelectedObject() == 0 ) { + + // clear player's action too + StateObjectInstance *objZero = + *( mGameStateToEdit->mObjects.getElement( 0 ) ); + + + objZero->mAction[0] = '\0'; + + // re-center action (now empty) on player + mGameStateToEdit->resetActionAnchor( 0 ); + } + + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + + checkFlipAndDeleteSpeechButtonEnabled(); + } + else if( inTarget == mFlipSpeechButton ) { + saveUndoPoint(); + + int selObject = mGameStateToEdit->getSelectedObject(); + + StateObjectInstance *o = + *( mGameStateToEdit->mObjects.getElement( selObject ) ); + + o->mSpeechOffset.x += + mGameStateToEdit->getAutoOffsetOnFlip( selObject ); + + mGameStateToEdit->flipSelectedSpeech(); + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mSpeechBubbleBoxButton ) { + saveUndoPoint(); + + mGameStateToEdit->setSelectedSpeechBox( + mSpeechBubbleBoxButton->getState() ); + + checkFlipAndDeleteSpeechButtonEnabled(); + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mGridButton ) { + // toggle + mGridButton->setSelected( ! mGridButton->getSelected() ); + + mStateDisplay->showGrid( mGridButton->getSelected() ); + } + else if( inTarget == mFreezeButton ) { + // toggle + mFreezeButton->setSelected( ! mFreezeButton->getSelected() ); + + saveUndoPoint(); + + mGameStateToEdit->freezeObjectZero( mFreezeButton->getSelected() ); + + // don't need to update display at all + } + else if( inTarget == mLockSelectedButton ) { + // toggle + mLockSelectedButton->setSelected( + ! mLockSelectedButton->getSelected() ); + + saveUndoPoint(); + + + mGameStateToEdit->setSelectedLocked( + mLockSelectedButton->getSelected() ); + + if( mGameStateToEdit->mLocksOn ) { + // look for an unlocked object to make selected + char objChange = false; + + while( mGameStateToEdit->getSelectedLocked() ) { + mGameStateToEdit->setSelectedObject( + mGameStateToEdit->getSelectedObject() - 1 ); + objChange = true; + } + if( objChange ) { + setSelectedObject( mGameStateToEdit->getSelectedObject() ); + } + } + + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mLockGlobalButton ) { + mLockGlobalButton->setSelected( ! mLockGlobalButton->getSelected() ); + + saveUndoPoint(); + + mGameStateToEdit->mLocksOn = mLockGlobalButton->getSelected(); + + if( mGameStateToEdit->mLocksOn ) { + // look for an unlocked object to make selected + char objChange = false; + + while( mGameStateToEdit->getSelectedLocked() ) { + mGameStateToEdit->setSelectedObject( + mGameStateToEdit->getSelectedObject() - 1 ); + objChange = true; + } + if( objChange ) { + setSelectedObject( mGameStateToEdit->getSelectedObject() ); + } + } + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mHoldObjectButton ) { + // toggle + mHoldObjectButton->setSelected( + ! mHoldObjectButton->getSelected() ); + + saveUndoPoint(); + + + mGameStateToEdit->setObjectHeld( + mGameStateToEdit->getSelectedObject(), + mHoldObjectButton->getSelected() ); + + // don't update anything display-wise + } + else if( inTarget == mRoomTransSlider && ! mIgnoreSliders ) { + if( mRoomTransSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + mGameStateToEdit->mRoomTrans = + (unsigned char)( 255 * mRoomTransSlider->getThumbPosition() ); + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mObjectTransSlider && ! mIgnoreSliders ) { + if( mObjectTransSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + mGameStateToEdit->adjustSelectedTrans( + (unsigned char)( 255 * mObjectTransSlider->getThumbPosition() ) ); + + // don't redo sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else if( inTarget == mClearButton ) { + saveUndoPoint(); + + mGameStateToEdit->mRoom = Room::getDefaultResource(); + + mGameStateToEdit->mRoomTrans = 255; + + + mGameStateToEdit->deleteSelectedObject(); + + int numObjects = mGameStateToEdit->mObjects.size(); + + // don't delete obj 0 + for( int i=numObjects-1; i>0; i-- ) { + mGameStateToEdit->setSelectedObject( i ); + mGameStateToEdit->deleteSelectedObject(); + } + // obj 0 left and selected + mGameStateToEdit->changeSelectedObject( + StateObject::getDefaultResource() ); + + mGameStateToEdit->deleteAllCharsFromSelectedSpeech(); + + // revert + mGameStateToEdit->adjustSelectedTrans( 255 ); + + + // leave action in place (controller cannot edit it) + + + // mStateDisplay->setState( mGameStateToEdit->copy() ); + mUndoOrRedoOrClearAction = true; + + setGameStateToEdit( mGameStateToEdit->copy() ); + + mUndoOrRedoOrClearAction = false; + } + else if( inTarget == mStateDisplay + && + !mRoomTransSlider->mDragging + && + !mObjectTransSlider->mDragging ) { + // ignore display events if dragging off edge of slider + + + + + /* + if( mStateDisplay->mLastActionRelease ) { + // grab hit room tile and set it as bg tile for other pickers + uniqueID hitTileID = mGameStateToEdit->mRoom.getTile( + mStateDisplay->mLastGridClickX, + G - mStateDisplay->mLastGridClickY - 1 ); + + Tile hitTile( hitTileID ); + + mainTilePicker->setBackgroundTile( hitTile ); + } + */ + + + + if( mStateDisplay->mLastActionRelease && + mainDragAndDrop->isDragging() ) { + + // add new obj + + saveUndoPoint(); + + if( mGameStateToEdit->canAdd() ) { + + StateObject resource = + mainStateObjectPicker->getDraggedResource(); + + + mGameStateToEdit->newObject( + mStateDisplay->mLastGridClickX, + mStateDisplay->mLastGridClickY, + resource ); + } + + setGameStateToEdit( mGameStateToEdit->copy() ); + } + else if( !mainDragAndDrop->isDragging() ) { + + + + if( mToolSet->getSelected() == move ) { + // pick object anchor whenever we press over it with move tool + // tool it is + if( mStateDisplay->mLastActionPress ) { + + int objIndex = mGameStateToEdit->getHitObject( + mStateDisplay->mLastGridClickX, + mStateDisplay->mLastGridClickY ); + + if( objIndex != -1 ) { + int oldSelected = + mGameStateToEdit->getSelectedObject(); + + if( oldSelected != objIndex ) { + mObjectChanging = true; + + saveUndoPoint(); + + setSelectedObject( objIndex ); + } + } + } + } + + + // allow typing whenever, even if not in speech mode + // but never let Controller type into Player's bubble + if( mStateDisplay->mLastActionKeyPress && + mGameStateToEdit->getSelectedObject() != 0 ) { + unsigned char key = mStateDisplay->mLastKeyPressed; + + if( key == 127 || key == 8 ) { + // backspace and delete + saveUndoPoint(); + + mGameStateToEdit->deleteCharFromSelectedSpeech(); + } + else if( (key >= 32 && key <= 126) + || key >= 160 ) { + // allowed range, ascii and extended ascii + + // only save undo points before a space is pressed + // or before first chars are typed + if( key == 32 || + mGameStateToEdit->getSelectedSpeechLength() == + 0 ) { + + saveUndoPoint(); + } + + mGameStateToEdit->addCharToSelectedSpeech( + (char)key ); + } + + checkFlipAndDeleteSpeechButtonEnabled(); + + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + + + + + switch( mToolSet->getSelected() ) { + case move: { + if( !mStateDisplay->mLastActionKeyPress ) { + + // only save an undo point when mouse initially pressed + // (ignore micro-state changes until release) + // undo point already saved if object changing + if( !mObjectChanging && + mStateDisplay->mLastActionPress ) { + saveUndoPoint(); + } + + // move even in response to mouse dragging + + // whole object only + mGameStateToEdit->moveSelectedObject( + mStateDisplay->mLastGridClickX, + mStateDisplay->mLastGridClickY ); + + // no need to generate all new sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + + // flip might become forbidden because of move + checkFlipAndDeleteSpeechButtonEnabled(); + + getTileUnder(); + } + } + break; + case speak: { + if( !mStateDisplay->mLastActionKeyPress ) { + // mouse movement with speech tool + // adjust speech anchor + + // ignore this if this mouse action is picking + // a new object + if( !mObjectChanging ) { + + // only save an undo point when mouse + // initially pressed + // (ignore micro-state changes until release) + if( mStateDisplay->mLastActionPress ) { + saveUndoPoint(); + } + + + mGameStateToEdit->moveSelectedSpeechAnchor( + mStateDisplay->mLastPixelClickX, + mStateDisplay->mLastPixelClickY ); + + + // no need to generate all new sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + mStateDisplay->mManualBubblePositioningLive = true; + + // flip might become forbidden because of move + checkFlipAndDeleteSpeechButtonEnabled(); + } + } + + } + break; + } + + + // if mouse released, object change is over + if( mStateDisplay->mLastActionRelease ) { + mObjectChanging = false; + } + } + + } + + + if( ! mNameField->isFocused() && + ! mainStateObjectPicker->isSearchFieldFocused() && + ! mainScenePicker->isSearchFieldFocused() ) { + + + // always refocus whenever an action happens + // essentially, keep focus on display always + // (except when user edits Scene name or types in a search box) + mStateDisplay->setFocus( true ); + } + + } + + + +void GameStateEditor::editorClosing() { + } diff --git a/gameSource/GameStateEditor.h b/gameSource/GameStateEditor.h new file mode 100644 index 0000000..7f3bf9b --- /dev/null +++ b/gameSource/GameStateEditor.h @@ -0,0 +1,157 @@ +#ifndef GAME_STATE_EDITOR_INCLUDED +#define GAME_STATE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "GameState.h" +#include "GameStateDisplay.h" +#include "StateToolSet.h" +#include "SizeLimitedVector.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" +#include "ToolTipSliderGL.h" + + + +class GameStateEditor : public Editor { + + public: + + GameStateEditor( ScreenGL *inScreen ); + + ~GameStateEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + GameState *mGameStateToEdit; + + void setGameStateToEdit( GameState *inGameState, + char inUpdatePickers = true ); + + void enableSend( char inEnabled ); + void aboutToSend(); + + + // to receive redraw pings from display + void displayRedrawed(); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + + GameStateDisplay *mStateDisplay; + + char mEditingSelectedObject; + + + EditButtonGL *mEditStateObjectButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + ClearButtonGL *mClearButton; + + char mUndoOrRedoOrClearAction; + char mIgnoreSliders; + + void clearRedoStack(); + + void saveUndoPoint(); + + + StateToolSet *mToolSet; + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + AddButtonGL *mAddSceneButton; + char mAddSceneAction; + + + QuickDeleteButtonGL *mSpeechDeleteButton; + + + ToggleSpriteButtonGL *mSpeechBubbleBoxButton; + + + FlipHButtonGL *mFlipSpeechButton; + + // checks based on currently-selected object and how + // close it is to screen (also whether speech bubble is even visible) + // also checks cases for enabling speech delete button + void checkFlipAndDeleteSpeechButtonEnabled(); + + + void setSelectedObject( int inIndex ); + + + void getTileUnder(); + + + SelectableButtonGL *mGridButton; + + SelectableButtonGL *mFreezeButton; + + SelectableButtonGL *mLockGlobalButton; + SelectableButtonGL *mLockSelectedButton; + + + SelectableButtonGL *mHoldObjectButton; + + + SpriteButtonGL *mSetObjectButton; + KeyEquivButtonGL *mAddObjectButton; + KeyEquivButtonGL *mEditObjectButton; + KeyEquivButtonGL *mRemoveObjectButton; + + + SpriteButtonGL *mEditRoomButton; + + + + SendButtonGL *mSendButton; + + SpriteButtonGL *mPracticeButton; + + + SpriteButtonGL *mEditMusicButton; + + + ToolTipSliderGL *mRoomTransSlider; + ToolTipSliderGL *mObjectTransSlider; + + + TextFieldGL *mNameField; + + + ToggleSpriteButtonGL *mAddToPackButton; + SpriteButtonGL *mSavePackButton; + + + Image *mNoDropImage; + Image *mCanDropImage; + + + + + char mIgnoreObjPickerEvents; + + + // don't move speech anchor while this is happening + char mObjectChanging; + + + // for usage tracking of objects in current state + uniqueID mCurrentWorkingStateID; + + + + }; + +#endif diff --git a/gameSource/GridOverlay.cpp b/gameSource/GridOverlay.cpp new file mode 100644 index 0000000..51f11f7 --- /dev/null +++ b/gameSource/GridOverlay.cpp @@ -0,0 +1,38 @@ +#include "GridOverlay.h" + +#include + + + +GridOverlay::GridOverlay( double inAnchorX, double inAnchorY, + double inWidth, double inHeight, + int inNumCells, Color inColor ) + : GUIComponentGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mNumCells( inNumCells ), + mColor( inColor ) { + + } + + +void GridOverlay::fireRedraw() { + + + double cellWidth = mWidth / mNumCells; + double cellHeight = mHeight / mNumCells; + + + glColor4f( mColor.r, mColor.g, mColor.b, mColor.a ); + glBegin( GL_LINES ); { + + for( int x=0; xgetFontColor()->copy(); + + mText->setFontColor( mHighlightColor.copy() ); + + // lighten background to further highlight label + glColor4f( 1, 1, 1, 0.25 ); + + double drawWidth = mText->measureTextWidth( mString ) * mHeight; + + glColor4f( mHighlightColor.r, mHighlightColor.g, mHighlightColor.b, + 0.125 ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX - 1, mAnchorY - 1 ); + glVertex2d( mAnchorX + drawWidth + 1, mAnchorY - 1 ); + glVertex2d( mAnchorX + drawWidth + 1, mAnchorY + mHeight + 1 ); + glVertex2d( mAnchorX - 1, mAnchorY + mHeight + 1 ); + } + glEnd(); + } + + mText->drawText( mString, mAnchorX, mAnchorY, + mWidth, mHeight ); + + if( mHighlightOn ) { + mText->setFontColor( oldColor ); + } + + } + + +inline void HighlightLabelGL::mousePressed( double inX, double inY ) { + if( isEnabled() ) { + // fire an event + fireActionPerformed( this ); + } + } + + +#endif + + + diff --git a/gameSource/HueValuePicker.cpp b/gameSource/HueValuePicker.cpp new file mode 100644 index 0000000..3f908c9 --- /dev/null +++ b/gameSource/HueValuePicker.cpp @@ -0,0 +1,146 @@ +#include "HueValuePicker.h" + +#include "common.h" + + +HueValuePicker::HueValuePicker( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : BorderPanel( inAnchorX - 2, inAnchorY - 2, + inWidth + 4, inHeight + 4, + new Color( 0, 0, 0, 1 ), + new Color( 0.35, 0.35, 0.35, 1 ), + 1 ), + mPressed( false ) { + + mH = 0; + mV = 1; + + + double innerW = mWidth -2; + double innerH = mHeight -2; + + mFieldImage = new Image( (int)innerW, (int)innerH, 3, false ); + + double *channels[3] = + { mFieldImage->getChannel( 0 ), + mFieldImage->getChannel( 1 ), + mFieldImage->getChannel( 2 ) }; + + for( int y=0; yr; + channels[1][index] = c->g; + channels[2][index] = c->b; + + delete c; + } + } + + //writeTGA( mFieldImage, "testHV.tga" ); + + mFieldSprite = new Sprite( mFieldImage ); + //mFieldSprite = new Sprite( "../testHV.tga" ); + + mColorSpot = new Sprite( "colorSpot.tga", true ); + } + + + + +HueValuePicker::~HueValuePicker() { + delete mFieldImage; + delete mFieldSprite; + delete mColorSpot; + } + + + +float HueValuePicker::getSelectedHue() { + return mH; + } + +float HueValuePicker::getSelectedValue() { + return mV; + } + + + +void HueValuePicker::setValues( float inHue, float inValue ) { + mH = inHue; + mV = inValue; + } + + + +void HueValuePicker::mouseActivity( double inX, double inY ) { + if( isInside( inX, inY ) && mPressed ) { + + inY --; + + double innerW = mWidth -3; + double innerH = mHeight -3; + if( inX < mAnchorX + 1 ) { + inX = mAnchorX + 1; + } + if( inY < mAnchorY + 1 ) { + inY = mAnchorY + 1; + } + + if( inX > mAnchorX + 1 + innerW ) { + inX = mAnchorX + 1 + innerW; + } + if( inY > mAnchorY + 1 + innerH ) { + inY = mAnchorY + 1 + innerH; + } + + + mH = (float)(inX - (mAnchorX + 1)) / innerW; + mV = (float)(inY - (mAnchorY + 1)) / innerH; + + fireActionPerformed( this ); + } + } + + +void HueValuePicker::mousePressed( double inX, double inY ) { + mPressed = true; + mouseActivity( inX, inY ); + } + +void HueValuePicker::mouseDragged( double inX, double inY ) { + mouseActivity( inX, inY ); + } + +void HueValuePicker::mouseReleased( double inX, double inY ) { + mouseActivity( inX, inY ); + mPressed = false; + } + + + +void HueValuePicker::fireRedraw() { + BorderPanel::fireRedraw(); + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight / 2, 0 ); + + mFieldSprite->draw( 0, 0, &pos ); + + double innerW = mWidth -3; + double innerH = mHeight -3; + + Vector3D spotPos( mAnchorX + 1 + innerW * mH, + mAnchorY + 2 + innerH * mV, 0 ); + + mColorSpot->draw( 0, 0, &spotPos ); + } + + + diff --git a/gameSource/HueValuePicker.h b/gameSource/HueValuePicker.h new file mode 100644 index 0000000..36d2a45 --- /dev/null +++ b/gameSource/HueValuePicker.h @@ -0,0 +1,73 @@ +#ifndef HUE_VALUE_PICKER_INCLUDED +#define HUE_VALUE_PICKER_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" +#include "minorGems/ui/event/ActionListenerList.h" + +#include "minorGems/graphics/Image.h" + +#include "color.h" +#include "BorderPanel.h" +#include "Sprite.h" + + +class HueValuePicker : public BorderPanel, public ActionListenerList { + + + public: + + + + // w and h MUST be powers of two! + HueValuePicker( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + + + virtual ~HueValuePicker(); + + + // 0-1 + float getSelectedHue(); + float getSelectedValue(); + + + void setValues( float inHue, float inValue ); + + + + + // override fireRedraw in GUIComponentGL + virtual void mousePressed( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + virtual void fireRedraw(); + + + // true if currently active from an on-picker press + char mPressed; + + + protected: + + void mouseActivity( double inX, double inY ); + + Image *mFieldImage; + Sprite *mFieldSprite; + + Sprite *mColorSpot; + + float mH; + float mV; + + + + + }; + + + +#endif + + + diff --git a/gameSource/Makefile.all b/gameSource/Makefile.all new file mode 100644 index 0000000..69a28b3 --- /dev/null +++ b/gameSource/Makefile.all @@ -0,0 +1,104 @@ +# +# Modification History +# +# 2006-June-27 Jason Rohrer +# Created. Copied from Transcend. +# + + +## +# The portion of SleepIsDeath Makefiles common to all platforms. +# +# Should not be made manually--- +# used by SleepIsDeath/configure to build Makefiles. +## + + + + +ROOT_PATH = ../.. + + + +include makeFileList + + + +LAYER_OBJECTS = ${LAYER_SOURCE:.cpp=.o} + + + + + + + + +TEST_SOURCE = +TEST_OBJECTS = ${TEST_SOURCE:.cpp=.o} + + + +# separate dependency files +LAYER_DEPENDS = ${LAYER_SOURCE:.cpp=.dep} + + +# targets + +all: SleepIsDeath ${GAME_GRAPHICS} +clean: + rm -f ${LAYER_DEPENDS} ${LAYER_OBJECTS} ${TEST_OBJECTS} ${NEEDED_MINOR_GEMS_OBJECTS} SleepIsDeath ${GAME_GRAPHICS} + + +graphics: ${GAME_GRAPHICS} + + +SleepIsDeath: ${LAYER_OBJECTS} ${NEEDED_MINOR_GEMS_OBJECTS} ${ICON_FILE} + ${EXE_LINK} -o SleepIsDeath ${LAYER_OBJECTS} ${NEEDED_MINOR_GEMS_OBJECTS} ${ICON_FILE} ${COMMON_LIBS} ${PLATFORM_LINK_FLAGS} + + +# layer objects w/out game.o +LAYER_OBJECTS_NO_MAIN = ${LAYER_OBJECTS:game.o=gameNoMain.o} + +testUsage: ${LAYER_OBJECTS_NO_MAIN} ${NEEDED_MINOR_GEMS_OBJECTS} testUsage.o + ${EXE_LINK} -o testUsage testUsage.o ${LAYER_OBJECTS_NO_MAIN} ${NEEDED_MINOR_GEMS_OBJECTS} ${COMMON_LIBS} ${PLATFORM_LINK_FLAGS} + + +# add this on Unix to support JPEG video frame output +# -ljpeg ${ROOT_PATH}/minorGems/graphics/converters/unix/JPEGImageConverterUnix.cpp + + + + + +# +# Generic: +# +# Map all .cpp files into .dep files +# +# $@ represents the name.dep file +# $< represents the name.cpp file +# +%.dep: %.cpp + ${COMPILE} -MM $< >> $@ + +# include them all +include ${LAYER_DEPENDS} + + + + + +# +# Generic: +# +# Map all png files into .tga files +# +# $@ represents the name.tga file +# $< represents the name.png file +# +graphics/%.tga: %.png + convert $< $@ +music/%.tga: %.png + convert $< $@ + + diff --git a/gameSource/MoveToolSet.cpp b/gameSource/MoveToolSet.cpp new file mode 100644 index 0000000..1049644 --- /dev/null +++ b/gameSource/MoveToolSet.cpp @@ -0,0 +1,95 @@ +#include "MoveToolSet.h" + + + +MoveToolSet::MoveToolSet( double inAnchorX, double inAnchorY ) + : GUIPanelGL( inAnchorX, inAnchorY, 60, 20, + new Color( 0, 0, 0, 0 ) ) { + + mActButton = new SelectableButtonGL( + new Sprite( "act.tga", true ), + 1, + inAnchorX, inAnchorY, 20, 20 ); + + mMoveButton = new SelectableButtonGL( + new Sprite( "move.tga", true ), + 1, + inAnchorX + 20, inAnchorY, 20, 20 ); + + mSpeakButton = new SelectableButtonGL( + new Sprite( "speak.tga", true ), + 1, + inAnchorX + 40, inAnchorY, 20, 20 ); + + mActButton->setToolTip( "tip_playerAct" ); + mMoveButton->setToolTip( "tip_playerMove" ); + mSpeakButton->setToolTip( "tip_playerSpeak" ); + + add( mActButton ); + add( mMoveButton ); + add( mSpeakButton ); + + mActButton->addActionListener( this ); + mMoveButton->addActionListener( this ); + mSpeakButton->addActionListener( this ); + + mMoveButton->setSelected( true ); + } + + +void MoveToolSet::setMoveAllowed( char inAllowed ) { + if( !inAllowed && getSelected() == playerMove ) { + setSelected( playerSpeak ); + //setSelected( playerAct ); + // change + fireActionPerformed( this ); + } + + mMoveButton->setEnabled( inAllowed ); + } + + + +moveTool MoveToolSet::getSelected() { + if( mActButton->getSelected() ) { + return playerAct; + } + if( mMoveButton->getSelected() ) { + return playerMove; + } + if( mSpeakButton->getSelected() ) { + return playerSpeak; + } + + return playerMove; + } + + + +void MoveToolSet::setSelected( moveTool inTool ) { + mActButton->setSelected( inTool == playerAct ); + mMoveButton->setSelected( inTool == playerMove ); + mSpeakButton->setSelected( inTool == playerSpeak ); + } + + + +void MoveToolSet::actionPerformed( GUIComponent *inTarget ) { + // select hit, turn others off + + mActButton->setSelected( inTarget == mActButton ); + mMoveButton->setSelected( inTarget == mMoveButton ); + mSpeakButton->setSelected( inTarget == mSpeakButton ); + + fireActionPerformed( this ); + } + + +void MoveToolSet::setEnabled( char inEnabled ) { + GUIPanelGL::setEnabled( inEnabled ); + + mActButton->setEnabled( inEnabled ); + mMoveButton->setEnabled( inEnabled ); + mSpeakButton->setEnabled( inEnabled ); + } + diff --git a/gameSource/MoveToolSet.h b/gameSource/MoveToolSet.h new file mode 100644 index 0000000..2e629ae --- /dev/null +++ b/gameSource/MoveToolSet.h @@ -0,0 +1,48 @@ +#ifndef MOVE_TOOL_SET_INCLUDED +#define MOVE_TOOL_SET_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/ui/event/ActionListener.h" +#include "minorGems/ui/event/ActionListenerList.h" + +#include "buttons.h" + + +enum moveTool { playerMove, playerSpeak, playerAct }; + + +class MoveToolSet : public GUIPanelGL, public ActionListener, + public ActionListenerList { + + + public: + + // sets its width/height automatically + MoveToolSet( double inAnchorX, double inAnchorY ); + + + moveTool getSelected(); + + void setSelected( moveTool inTool ); + + void setMoveAllowed( char inAllowed ); + + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + virtual void setEnabled( char inEnabled ); + + + protected: + + SelectableButtonGL *mActButton; + SelectableButtonGL *mMoveButton; + SelectableButtonGL *mSpeakButton; + + + }; + + +#endif diff --git a/gameSource/Music.cpp b/gameSource/Music.cpp new file mode 100644 index 0000000..e31a8aa --- /dev/null +++ b/gameSource/Music.cpp @@ -0,0 +1,349 @@ +#include "Music.h" +#include "resourceManager.h" + +#include "imageCache.h" + +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + +// for grid display, color subsequent octaves +Color lowColor( 0, 146.0/255, 219.0/255 ); +Color medColor( 146.0/255, 219.0/255, 109/255 ); +Color hiColor( 219.0/255, 146.0/255, 0 ); + + +// for sprites +// from Scale.cpp +extern Color toneLowColor; +extern Color toneHighColor; + + + + +// static inits +Music *Music::sBlankMusic = NULL; + +void Music::staticInit() { + sBlankMusic = new Music(); + // ensure that blank music is saved at least once + sBlankMusic->finishEdit(); + } + +void Music::staticFree() { + delete sBlankMusic; + } + + + + +void Music::setupDefault() { + memset( (void *)mNotes, 0, mNoteDataLength ); + + + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +Music::Music() { + setupDefault(); + } + + + +char Music::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mNoteDataLength ) { + + memcpy( (void *)mNotes, inData, mNoteDataLength ); + + inLength -= mNoteDataLength; + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, &inData[mNoteDataLength], inLength ); + return true; + } + } + return false; + } + + + +Music::Music( uniqueID inID, unsigned char *inData, int inLength ) + : mID( inID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + + +Music::Music( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "music", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + + if( ! initFromData( data, length ) ) { + // failure + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // music failed to load + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + } + + + +void Music::editMusic( int inX, int inY, char inNoteOn ) { + mNotes[inY][inX] = inNoteOn; + } + + + +char Music::getNoteOn( int inX, int inY ) { + return mNotes[inY][inX]; + } + + + +void Music::editMusicName( const char *inName ) { + int newLength = strlen( inName ); + if( newLength > 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Music set name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + +#include "minorGems/util/stringUtils.h" + + +char *Music::getMusicName() { + return stringDuplicate( mName ); + } + + + +#include "minorGems/util/SimpleVector.h" + + +unsigned char *Music::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( (unsigned char *)mNotes, + mNoteDataLength ); + + dataAccum.push_back( (unsigned char *)mName, + strlen( mName ) + 1 ); + + + *outLength = dataAccum.size(); + + return dataAccum.getElementArray(); + } + + + + +void Music::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( ! equal( oldID, mID ) || + ! resourceExists( "music", mID ) ) { + + // change + + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + saveResourceData( "music", mID, + mName, + data, dataSize ); + + delete [] data; + } + } + + + +void Music::saveToPack() { + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + addToPack( "music", mID, + mName, + data, dataSize ); + + delete [] data; + } + + + + +uniqueID Music::getUniqueID() { + return mID; + } + + + +const char *Music::getResourceType() { + return "music"; + } + +Music Music::getDefaultResource() { + return *( Music::sBlankMusic ); + } + + + + +void Music::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, + (unsigned char*)mNotes, mNoteDataLength ); + + p = addToUniqueID( p, (unsigned char*)mName, strlen( mName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *Music::getImage() { + Image *image = new Image( N, N, 4, true ); + + double *channels[4]; + + int i; + + for( i=0; i<4; i++ ) { + channels[i] = image->getChannel( i ); + } + + Color *drawColor = &lowColor; + + //Color *drawColorA = &lowColor;//&toneLowColor; + //Color *drawColorB = &medColor;//&toneHighColor; + Color drawColorA( 0.50, 0.50, 0.50 ); + Color drawColorB( 1.0, 1.0, 1.0 ); + + + for( int y=0; y4 ) { + drawColor = &medColor; + } + if( y>9 ) { + drawColor = &hiColor; + } + + Color *levelColor = Color::linearSum( &drawColorB, &drawColorA, + y / (float)N ); + + int pixY = N - y - 1; + + for( int x=0; x +void SizeLimitedVector::deleteElementOfType( + Music inElement ) { + // no delete necessary + } + + + +MusicEditor::MusicEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_music" ); + + mSidePanel->add( mainMusicPicker ); + + mainMusicPicker->addActionListener( this ); + + + mNoteDisplay = new NoteGridDisplay( 8, gameWidth - 48 - N * W ); + mMainPanel->add( mNoteDisplay ); + + mNoteDisplay->addActionListener( this ); + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "musicName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + N * W; + + double extra = gameHeight - gridEdge; + + + // center it vertically on music picker + double addY = mainMusicPicker->getAnchorY() + + mainMusicPicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addMusic" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + + mTransformToolSet = new TransformToolSet( sideButtonsX, + mMiniViewButton->getAnchorY() + - 4 - 100 ) ; + + mMainPanel->add( mTransformToolSet ); + + mTransformToolSet->addActionListener( this ); + + + + + double undoButtonY = gameWidth - ( 48 + N * W ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + + setMusicToEdit( mainMusicPicker->getSelectedResource() ); + + postConstruction(); + } + + + +MusicEditor::~MusicEditor() { + mSidePanel->remove( mainMusicPicker ); + } + + + +void MusicEditor::setMusicToEdit( Music inMusic ) { + mMusicToEdit = inMusic; + + for( int y=0; ysetText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + } + + + +void MusicEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mMusicToEdit.getSprite( false, false ) ); + } + + + +void MusicEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainMusicPicker ) { + if( ! mAddAction && + ! mainMusicPicker->wasLastActionFromPress() ) { + // will change music + + mUndoStack.push_back( mMusicToEdit ); + mUndoButton->setEnabled( true ); + + setMusicToEdit( mainMusicPicker->getSelectedResource() ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mNameField ) { + mUndoStack.push_back( mMusicToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mMusicToEdit.editMusicName( mNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addMusic(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Music last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mMusicToEdit ); + mRedoButton->setEnabled( true ); + + setMusicToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Music next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mMusicToEdit ); + mUndoButton->setEnabled( true ); + + setMusicToEdit( next ); + } + else if( inTarget == mTransformToolSet ) { + mUndoStack.push_back( mMusicToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + + char oldToggles[N][N]; + + for( int y=0; ygetLastPressed() ) { + case flipH: { + for( int y=0; ymLastActionPress ) { + // note grid has changed, + // but our music object hasn't been touched yet, so we can + // still save it as an undo point + + mUndoStack.push_back( mMusicToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + + // copy changes into our object + for( int y=0; yresetColumnGlow(); + + // silence our extra part (only plays when editor open + partLengths[SI] = 0; + } + + + +void MusicEditor::addMusic() { + mAddAction = true; + mMusicToEdit.finishEdit(); + mainMusicPicker->setSelectedResource( mMusicToEdit, true ); + mAddAction = false; + } + diff --git a/gameSource/MusicEditor.h b/gameSource/MusicEditor.h new file mode 100644 index 0000000..6693063 --- /dev/null +++ b/gameSource/MusicEditor.h @@ -0,0 +1,70 @@ +#ifndef MUSIC_EDITOR_INCLUDED +#define MUSIC_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Music.h" +#include "TransformToolSet.h" +#include "SizeLimitedVector.h" + +#include "NoteGridDisplay.h" + + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class MusicEditor : public Editor { + + public: + + MusicEditor( ScreenGL *inScreen ); + + ~MusicEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addMusic(); + + + NoteGridDisplay *mNoteDisplay; + + + + AddButtonGL *mAddButton; + char mAddAction; + + void setMusicToEdit( Music inMusic ); + + void refreshMiniView(); + + + SpriteButtonGL *mMiniViewButton; + + Music mMusicToEdit; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + TransformToolSet *mTransformToolSet; + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mNameField; + + }; + +#endif diff --git a/gameSource/MusicPicker.cpp b/gameSource/MusicPicker.cpp new file mode 100644 index 0000000..3cdb5ea --- /dev/null +++ b/gameSource/MusicPicker.cpp @@ -0,0 +1,17 @@ +#include "MusicPicker.h" + +// all share one stack +SimpleVector globalMusicStack; + + +MusicPicker::MusicPicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalMusicStack ) { + } + + + + +MusicPicker::~MusicPicker() { + + } diff --git a/gameSource/MusicPicker.h b/gameSource/MusicPicker.h new file mode 100644 index 0000000..d6a2343 --- /dev/null +++ b/gameSource/MusicPicker.h @@ -0,0 +1,40 @@ +#ifndef MUSIC_PICKER_INCLUDED +#define MUSIC_PICKER_INCLUDED + +#include "Music.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class MusicPicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + MusicPicker( double inAnchorX, double inAnchorY ); + + + + virtual ~MusicPicker(); + + }; + + + +#endif + + + diff --git a/gameSource/NoteGridDisplay.cpp b/gameSource/NoteGridDisplay.cpp new file mode 100644 index 0000000..e134d90 --- /dev/null +++ b/gameSource/NoteGridDisplay.cpp @@ -0,0 +1,172 @@ +#include "NoteGridDisplay.h" +#include "Song.h" +#include "musicPlayer.h" +#include "ScalePicker.h" + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; + +extern int lastNoteColumnPlayed; + + +extern ScalePicker *mainScalePicker; + + + +NoteGridDisplay::NoteGridDisplay( int inAnchorX, int inAnchorY ) + : GUIComponentGL( inAnchorX, inAnchorY, N * W, N * W ), + mLastActionRelease( false ), mLastActionPress( false ) { + + Scale s = mainScalePicker->getSelectedResource(); + mNotesInOctave = s.getNumOn(); + + mNoteSprite = new Sprite( "note.tga", true ); + mNoteSpaceSprite = new Sprite( "noteSpace.tga", true ); + + for( int i=0; iaddActionListener( this ); + } + + +NoteGridDisplay::~NoteGridDisplay() { + delete mNoteSprite; + delete mNoteSpaceSprite; + } + + + +void NoteGridDisplay::resetColumnGlow() { + for( int x=0; x 0 ) { + + mColumnGlow[x] -= 0.0015; + if( mColumnGlow[x] < 0 ) { + mColumnGlow[x] = 0; + } + } + + fadeFactor = 0.4 + 0.6 * mColumnGlow[x]; + + + mNoteSpaceSprite->draw( 0, 0, &pos, 1, fadeFactor * 0.5, + drawColor ); + + // last instrument reserved for phrase display + if( noteToggles[SI][0][y][x] ) { + mNoteSprite->draw( 0, 0, &pos, 1, fadeFactor * 1.0, + drawColor ); + } + } + } + + } + + + +void NoteGridDisplay::mouseDragged( double inX, double inY ) { + mLastActionRelease = false; + mLastActionPress = false; + + if( isEnabled() && isInside( inX, inY ) ) { + + int x = (int)( (inX - mAnchorX) / W ); + int y = (int)( (inY - mAnchorY) / W ); + + noteToggles[SI][0][y][x] = mPressedInk; + + fireActionPerformed( this ); + } + } + + + + +void NoteGridDisplay::mousePressed( double inX, double inY ) { + mLastActionRelease = false; + mLastActionPress = true; + + if( isEnabled() && isInside( inX, inY ) ) { + + int x = (int)( (inX - mAnchorX) / W ); + int y = (int)( (inY - mAnchorY) / W ); + + + noteToggles[SI][0][y][x] = ! noteToggles[SI][0][y][x]; + + mPressedInk = noteToggles[SI][0][y][x]; + + fireActionPerformed( this ); + } + } + + + +void NoteGridDisplay::mouseReleased( double inX, double inY ) { + mLastActionRelease = true; + mLastActionPress = false; + + if( isEnabled() && isInside( inX, inY ) ) { + + int x = (int)( (inX - mAnchorX) / W ); + int y = (int)( (inY - mAnchorY) / W ); + + noteToggles[SI][0][y][x] = mPressedInk; + + fireActionPerformed( this ); + } + } + + + +void NoteGridDisplay::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mainScalePicker ) { + Scale s = mainScalePicker->getSelectedResource(); + mNotesInOctave = s.getNumOn(); + } + } + diff --git a/gameSource/NoteGridDisplay.h b/gameSource/NoteGridDisplay.h new file mode 100644 index 0000000..86a559f --- /dev/null +++ b/gameSource/NoteGridDisplay.h @@ -0,0 +1,62 @@ +#ifndef NOTE_GRID_DISPLAY_INCLUDED +#define NOTE_GRID_DISPLAY_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" +#include "minorGems/ui/event/ActionListenerList.h" +#include "minorGems/ui/event/ActionListener.h" + + +#include "Sprite.h" +#include "musicPlayer.h" + + +// note width +#define W 12 + + + +class NoteGridDisplay : public GUIComponentGL, public ActionListenerList, + public ActionListener { + + public: + + // sets its own width and height + NoteGridDisplay( int inAnchorX, int inAnchorY ); + + virtual ~NoteGridDisplay(); + + + void resetColumnGlow(); + + + + char mLastActionRelease; + char mLastActionPress; + + // override + virtual void fireRedraw(); + virtual void mouseDragged( double inX, double inY ); + virtual void mousePressed( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + int mNotesInOctave; + + char mPressedInk; + + Sprite *mNoteSprite; + + Sprite *mNoteSpaceSprite; + + + double mColumnGlow[N]; + + }; + + +#endif diff --git a/gameSource/Palette.cpp b/gameSource/Palette.cpp new file mode 100644 index 0000000..a4f9183 --- /dev/null +++ b/gameSource/Palette.cpp @@ -0,0 +1,318 @@ +#include "Palette.h" +#include "resourceManager.h" +#include "Room.h" + +#include "imageCache.h" +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + + +// static inits +Palette *Palette::sBlankPalette = NULL; + +void Palette::staticInit() { + sBlankPalette = new Palette(); + // ensure that blank palette is saved at least once + sBlankPalette->finishEdit(); + } + +void Palette::staticFree() { + delete sBlankPalette; + } + + + +void Palette::setupDefault() { + memset( (void *)mWellColors, 0, mWellDataLength ); + + + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +Palette::Palette() { + setupDefault(); + } + + + +char Palette::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mWellDataLength ) { + + memcpy( (void *)mWellColors, inData, mWellDataLength ); + + inLength -= mWellDataLength; + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, &inData[mWellDataLength], inLength ); + return true; + } + } + return false; + } + + + +Palette::Palette( uniqueID inID, unsigned char *inData, int inLength ) + : mID( inID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + + +Palette::Palette( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "palette", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + + if( ! initFromData( data, length ) ) { + // failure + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // palette failed to load + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + } + + + +void Palette::editPalette( int inIndex, rgbaColor inNewColor ) { + mWellColors[inIndex] = inNewColor; + } + + +rgbaColor Palette::getColor( int inIndex ) { + return mWellColors[inIndex]; + } + + + +void Palette::editPaletteName( const char *inName ) { + int newLength = strlen( inName ); + if( newLength > 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Palette set name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + +#include "minorGems/util/stringUtils.h" + + +char *Palette::getPaletteName() { + return stringDuplicate( mName ); + } + + + +#include "minorGems/util/SimpleVector.h" + +void Palette::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + if( ! equal( oldID, mID ) || + ! resourceExists( "palette", mID ) ) { + + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "palette", mID, + mName, + bytes, numBytes ); + + delete [] bytes; + } + } + + + +unsigned char *Palette::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( (unsigned char *)mWellColors, + mWellDataLength ); + + dataAccum.push_back( (unsigned char *)mName, + strlen( mName ) + 1 ); + + *outLength = dataAccum.size(); + return dataAccum.getElementArray(); + } + + + +void Palette::saveToPack() { + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + addToPack( "palette", mID, + mName, + bytes, numBytes ); + + delete [] bytes; + } + + + + +uniqueID Palette::getUniqueID() { + return mID; + } + + + +const char *Palette::getResourceType() { + return "palette"; + } + +Palette Palette::getDefaultResource() { + return *( Palette::sBlankPalette ); + } + + + + +void Palette::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, + (unsigned char*)mWellColors, mWellDataLength ); + + p = addToUniqueID( p, (unsigned char*)mName, strlen( mName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *Palette::getImage() { + Image *image = new Image( P, P, 3, false ); + + double *channels[3]; + + int i; + + for( i=0; i<3; i++ ) { + channels[i] = image->getChannel( i ); + } + + rgbaColor black = { { 0, 0, 0, 255 } }; + + + for( int y=0; y=0 && wellColumn < 5 ) { + c = mWellColors[ wellRow * 5 + wellColumn ]; + } + + + for( i=0; i<3; i++ ) { + + channels[i][pixel] = c.bytes[i] / 255.0; + } + } + } + + return image; + } + + + + +Sprite *Palette::getSprite( char inUseTrans, char inCacheOK ) { + // ignore inUseTrans + + if( inCacheOK ) { + + Image *cachedImage = getCachedImage( mID, false ); + + if( cachedImage == NULL ) { + cachedImage = getImage(); + + addCachedImage( mID, false, cachedImage ); + } + return new Sprite( cachedImage ); + } + else { + // ignore cache + Image *image = getImage(); + + Sprite *sprite = new Sprite( image ); + delete image; + return sprite; + } + } + + + +void Palette::print() { + char *idString = getHumanReadableString( mID ); + + printf( "Palette %s:\n", idString ); + + delete [] idString; + + printf( " name: %s\n", mName ); + + for( int i=0; i +void SizeLimitedVector::deleteElementOfType( + Palette inElement ) { + // no delete necessary + } + + + +PaletteEditor::PaletteEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_palette" ); + + mSidePanel->add( mainColorStack ); + + mainColorStack->addActionListener( this ); + + + mSidePanel->add( mainPalettePicker ); + + mainPalettePicker->addActionListener( this ); + + + mEditColorButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 7, + 8, + 8 ); + + mSidePanel->add( mEditColorButton ); + + mEditColorButton->addActionListener( this ); + mEditColorButton->setToolTip( "tip_edit_color" ); + + + double offset = P; + + double buttonSize = (gameHeight - 2 * offset - 8) / P; + + + rgbaColor c = { { 0,0,0,255 } }; + + Color unselectedBorder( 0.35, 0.35, 0.35, 1 ); + + double paletteButtonSize = 16; + double paletteButtonSpace = 24; + + for( int y=0; y<8; y++ ) { + for( int x=0; x<5; x++ ) { + + mButtonGrid[y][x] = new SpriteButtonGL( + NULL, + 1, + 51 + x * paletteButtonSpace, + gameWidth - ( 48 + (y + 1) * paletteButtonSpace ), + paletteButtonSize, + paletteButtonSize ); + + mButtonGrid[y][x]->setBorderColor( c ); + + GUIPanelGL *buttonBorderPanel = + new GUIPanelGL( mButtonGrid[y][x]->getAnchorX() - 1, + mButtonGrid[y][x]->getAnchorY() - 1, + mButtonGrid[y][x]->getWidth() + 2, + mButtonGrid[y][x]->getHeight() + 2, + unselectedBorder.copy() ); + buttonBorderPanel->add( mButtonGrid[y][x] ); + + + mMainPanel->add( buttonBorderPanel ); + + mButtonGrid[y][x]->addActionListener( this ); + } + } + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "paletteName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + P * buttonSize; + + double extra = gameHeight - gridEdge; + + + // center it vertically on palette picker + double addY = mainPalettePicker->getAnchorY() + + mainPalettePicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addPalette" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + double undoButtonY = gameWidth - ( 48 + P * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + + mAddToPackButton = new ToggleSpriteButtonGL( + new Sprite( "pack.tga", true ), + new Sprite( "packAlreadyIn.tga", true ), + 1, + mainPalettePicker->getAnchorX() + mainPalettePicker->getWidth() - 22, + mainPalettePicker->getAnchorY() + + mainPalettePicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mAddToPackButton ); + + mAddToPackButton->addActionListener( this ); + + mAddToPackButton->setToolTip( "tip_addPaletteToPack" ); + mAddToPackButton->setSecondToolTip( "tip_paletteAlreadyInPack" ); + + + mSavePackButton = new SpriteButtonGL( + new Sprite( "packSave.tga", true ), + 1, + mainPalettePicker->getAnchorX() + mainPalettePicker->getWidth() - 7, + mainPalettePicker->getAnchorY() + + mainPalettePicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mSavePackButton ); + + mSavePackButton->addActionListener( this ); + + mSavePackButton->setToolTip( "tip_savePack" ); + + + + + + setPaletteToEdit( mainColorStack->getPalette() ); + + + + postConstruction(); + } + + + +PaletteEditor::~PaletteEditor() { + mSidePanel->remove( mainColorStack ); + mSidePanel->remove( mainPalettePicker ); + + } + + + +void PaletteEditor::grabPaletteFromWells() { + + Palette p = mainColorStack->getPalette(); + + // preserve name as wells change + p.editPaletteName( mNameField->getText() ); + + setPaletteToEdit( p ); + } + + + + + +void PaletteEditor::setPaletteToEdit( Palette inPalette ) { + mPaletteToEdit = inPalette; + + for( int y=0; y<8; y++ ) { + for( int x=0; x<5; x++ ) { + + mButtonGrid[y][x]->setFillColor( + mPaletteToEdit.getColor( (8 - y - 1) * 5 + x ) ); + } + } + refreshMiniView(); + + char *name = mPaletteToEdit.getPaletteName(); + + mNameField->setText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + + mainColorStack->setPalette( mPaletteToEdit ); + } + + + +void PaletteEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mPaletteToEdit.getSprite( false, false ) ); + } + + + +void PaletteEditor::saveUndoPoint() { + mUndoStack.push_back( mPaletteToEdit ); + mUndoButton->setEnabled( true ); + + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + +void PaletteEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainColorStack ) { + if( mainColorStack->mLastActionWellChange ) { + // palette has changed + saveUndoPoint(); + + grabPaletteFromWells(); + } + } + else if( inTarget == mainPalettePicker ) { + if( ! mAddAction && + ! mainPalettePicker->wasLastActionFromPress() ) { + // will change palette + + saveUndoPoint(); + + Palette p = mainPalettePicker->getSelectedResource(); + setPaletteToEdit( p ); + + mAddToPackButton->setState( + alreadyInPack( mainPalettePicker->getSelectedResourceID() ) ); + } + } + else if( inTarget == mNameField ) { + saveUndoPoint(); + + mPaletteToEdit.editPaletteName( mNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addPalette(); + } + else if( inTarget == mEditColorButton ) { + // already in palette editor! + mainColorEditor->setEditPaletteButtonVisible( false ); + showColorEditor(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Palette last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mPaletteToEdit ); + mRedoButton->setEnabled( true ); + + setPaletteToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Palette next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mPaletteToEdit ); + mUndoButton->setEnabled( true ); + + setPaletteToEdit( next ); + } + else if( inTarget == mAddToPackButton ) { + AppLog::info( "Adding palette to the current resource pack" ); + mainPalettePicker->getSelectedResource().saveToPack(); + + mAddToPackButton->setState( true ); + } + else if( inTarget == mSavePackButton ) { + AppLog::info( "Saving the current resource pack" ); + savePack(); + + mAddToPackButton->setState( false ); + } + + } + + + +void PaletteEditor::editorClosing() { + addPalette(); + } + + + +void PaletteEditor::addPalette() { + mAddAction = true; + mPaletteToEdit.finishEdit(); + mainPalettePicker->setSelectedResource( mPaletteToEdit, true ); + mAddAction = false; + } + diff --git a/gameSource/PaletteEditor.h b/gameSource/PaletteEditor.h new file mode 100644 index 0000000..950303a --- /dev/null +++ b/gameSource/PaletteEditor.h @@ -0,0 +1,75 @@ +#ifndef PALETTE_EDITOR_INCLUDED +#define PALETTE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Palette.h" +#include "SizeLimitedVector.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class PaletteEditor : public Editor { + + public: + + PaletteEditor( ScreenGL *inScreen ); + + ~PaletteEditor(); + + + void grabPaletteFromWells(); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addPalette(); + + void saveUndoPoint(); + + + AddButtonGL *mAddButton; + char mAddAction; + + void setPaletteToEdit( Palette inPalette ); + + void refreshMiniView(); + + + SpriteButtonGL *mButtonGrid[8][5]; + + + SpriteButtonGL *mMiniViewButton; + + Palette mPaletteToEdit; + + + EditButtonGL *mEditColorButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mNameField; + + ToggleSpriteButtonGL *mAddToPackButton; + SpriteButtonGL *mSavePackButton; + + + }; + +#endif diff --git a/gameSource/PalettePicker.cpp b/gameSource/PalettePicker.cpp new file mode 100644 index 0000000..1962ac5 --- /dev/null +++ b/gameSource/PalettePicker.cpp @@ -0,0 +1,17 @@ +#include "PalettePicker.h" + +// all share one stack +SimpleVector globalPaletteStack; + + +PalettePicker::PalettePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalPaletteStack ) { + } + + + + +PalettePicker::~PalettePicker() { + + } diff --git a/gameSource/PalettePicker.h b/gameSource/PalettePicker.h new file mode 100644 index 0000000..e3dce77 --- /dev/null +++ b/gameSource/PalettePicker.h @@ -0,0 +1,40 @@ +#ifndef PALETTE_PICKER_INCLUDED +#define PALETTE_PICKER_INCLUDED + +#include "Palette.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class PalettePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + PalettePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~PalettePicker(); + + }; + + + +#endif + + + diff --git a/gameSource/PlayerGame.cpp b/gameSource/PlayerGame.cpp new file mode 100644 index 0000000..93f3ef6 --- /dev/null +++ b/gameSource/PlayerGame.cpp @@ -0,0 +1,313 @@ + + +#include "PlayerGame.h" + +#include "TimerDisplay.h" +#include "Connection.h" + +#include "ToolTipManager.h" + +#include "musicPlayer.h" + +#include "resourceManager.h" + +#include "Song.h" + + + + +#include "common.h" + +#include + + +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/SettingsManager.h" +#include "minorGems/util/log/AppLog.h" + + + +extern TimerDisplay *mainTimerDisplay; + + +extern Connection *connection; + + + + +PlayerGame::PlayerGame( ScreenGL *inScreen ) + : mConnected( true ) { + + setScreen( inScreen ); + + + // fetch failed resources from network + setUseNetwork( true ); + + + + char turnLengthFound = false; + mSecondsPerMove = SettingsManager::getIntSetting( "timeLimit", + &turnLengthFound ); + + if( !turnLengthFound ) { + mSecondsPerMove = 30; + } + + + mStepsPerSecond = 30; + resetTimer(); + + ToolTipManager::freeze( false ); + ToolTipManager::setTip( + (char*)TranslationManager::translate( "tip_waiting" ) ); + ToolTipManager::freeze( true ); + + + mEditor = new PlayerMoveEditor( inScreen ); + + mEditor->setVisible( true ); + //mEditor->addActionListener( this ); + + // wait for first move from player.... + mEditor->setMovesDisabled( true ); + mEditor->enableSend( false ); + + // for send button + mEditor->addActionListener( this ); + } + + +PlayerGame::~PlayerGame() { + mEditor->setVisible( false ); + + + delete mEditor; + } + + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; +extern int partLengths[PARTS]; + + + +void PlayerGame::resetTimer() { + mainTimerDisplay->setTime( 0 ); + + mStepsLeft = -1; + mLastStepTime = Time::getCurrentTime(); + } + + + + +void PlayerGame::step() { + + if( connection != NULL && connection->isConnected() ) { + + if( ! mConnected ) { + mConnected = true; + + if( mStepsLeft < 0 ) { + ToolTipManager::setTip( + (char*)TranslationManager::translate( "tip_waiting" ) ); + ToolTipManager::freeze( true ); + } + else { + // clear no connection + ToolTipManager::freeze( false ); + ToolTipManager::setTip( NULL ); + } + } + + + decrementStepCount(); + + mainTimerDisplay->freeze( false ); + mainTimerDisplay->setTime( mStepsLeft / mStepsPerSecond ); + + if( mStepsLeft > 0 ) { + mEditor->enableSend( true ); + } + } + else { + if( mConnected ) { + mConnected = false; + + + if( mStepsLeft < 0 ) { + ToolTipManager::freeze( false ); + + ToolTipManager::setTip( + (char*)TranslationManager::translate( + "tip_noConnection" ) ); + + ToolTipManager::freeze( true ); + } + } + + mainTimerDisplay->freeze( true ); + mEditor->enableSend( false ); + } + + + // printf( "Waiting for message\n" ); + + if( mStepsLeft == 0 ) { + // force a step here, regardless of system time + // we NEVER want to run step 0 more than once (send same + // state more than once) + mStepsLeft --; + + + if( connection != NULL ) { + + AppLog::info( "Sending game state\n" ); + + mEditor->enableSend( false ); + + // first, clear speech that player is done reading + mEditor->clearNonPlayerSpeech(); + + + int numBytes; + unsigned char *message = + mEditor->mGameStateToEdit->getStateAsMessage( + &numBytes ); + + connection->sendMessage( message, numBytes ); + + delete [] message; + + ToolTipManager::setTip( + (char*)TranslationManager::translate( "tip_waiting" ) ); + ToolTipManager::freeze( true ); + } + // wait for controler's response + mEditor->setMovesDisabled( true ); + } + else if( mStepsLeft < 0 ) { + // try to receive message from controller + if( connection != NULL ) { + + int numBytes; + unsigned char *message = connection->receiveMessage( &numBytes ); + + if( message != NULL ) { + AppLog::info( "Got message\n" ); + + GameState *state = new GameState( message, numBytes ); + + delete [] message; + + + + if( ! state->hasSong() ) { + + // copy received note toggles into music player + // try to recreat v13 timbre bands + for( int y=0; ymLiveSongReceived ); + + mLiveSong = state->mLiveSongReceived; + } + + + + + // do this *AFTER* snapshot is taken to avoid blank + // action arrows in snapshot (if player position frozen) + //mEditor->setMovesDisabled( false ); + + + mEditor->setGameStateToEdit( state ); + + + // stale now, because controller done editing it + // thus, it will be cleared right as player sends move + // (before taking flipbook screenshot, to avoid redundant + // speech in screenshots) + mEditor->mGameStateToEdit->markNonPlayerSpeechStale(); + + // player gets to move again + mStepsLeft = mStepsPerSecond * mSecondsPerMove; + mLastStepTime = Time::getCurrentTime(); + + // clear waiting tip + ToolTipManager::freeze( false ); + ToolTipManager::setTip( NULL ); + + mEditor->enableSend( true ); + } + } + } + + + } + + + +void PlayerGame::drawScene() { + + // right after we receive their move + // OR + // right after we sent our move + if( mStepsLeft == mStepsPerSecond * mSecondsPerMove + || + mStepsLeft == -1 ) { + + mEditor->saveFlipBookImage(); + + if( mStepsLeft == mStepsPerSecond * mSecondsPerMove ) { + // just received a move, and took a snapshot of it... + // now enable player to move + mEditor->setMovesDisabled( false ); + } + } + + } + + + +void PlayerGame::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mEditor ) { + // event from this means "send" + + AppLog::info( "Send button pressed\n" ); + + if( mStepsLeft > 0 ) { + // will send on very next step + mStepsLeft = 1; + mainTimerDisplay->setTime( 0 ); + } + else { + AppLog::error( + "ERROR: Send button pressed when mStepsLeft not positive" ); + } + + + } + + } + diff --git a/gameSource/PlayerGame.h b/gameSource/PlayerGame.h new file mode 100644 index 0000000..9ead896 --- /dev/null +++ b/gameSource/PlayerGame.h @@ -0,0 +1,33 @@ +#include "GameHalf.h" +#include "PlayerMoveEditor.h" + +#include "minorGems/ui/event/ActionListener.h" + + +class PlayerGame : public GameHalf, public ActionListener { + public: + + PlayerGame( ScreenGL *inScreen ); + + ~PlayerGame(); + + + virtual void resetTimer(); + + + virtual void step(); + virtual void drawScene(); + + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + protected: + PlayerMoveEditor *mEditor; + + char mConnected; + + // last live song received from Controller + Song mLiveSong; + }; + diff --git a/gameSource/PlayerMoveEditor.cpp b/gameSource/PlayerMoveEditor.cpp new file mode 100644 index 0000000..b15b801 --- /dev/null +++ b/gameSource/PlayerMoveEditor.cpp @@ -0,0 +1,1076 @@ +#include "PlayerMoveEditor.h" +#include "labels.h" + +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/SettingsManager.h" +#include "minorGems/util/log/AppLog.h" +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileOutputStream.h" +#include "minorGems/graphics/converters/PNGImageConverter.h" + +#include "minorGems/system/Time.h" + + +#include + + + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + + + +PlayerMoveEditor::PlayerMoveEditor( ScreenGL *inScreen ) + : Editor( inScreen, true, false ), // no side panel + mMovesDisabled( false ) { + + AppLog::info( "Constructing player move editor\n" ); + + // hide the close button for this editor, since it's the base editor + mCloseButton->setEnabled( false ); + + + + mStateDisplay = new GameStateDisplay( (gameWidth - G*P)/2, + gameWidth - 48 - G * P ); + mMainPanel->add( mStateDisplay ); + mStateDisplay->addActionListener( this ); + + // always hold focus, since there are no other keyboard-enabled + // components on screen + mStateDisplay->setFocus( true ); + mStateDisplay->lockFocus( true ); + + + + mToolSet = new MoveToolSet( mStateDisplay->getAnchorX(), 42 ); + mMainPanel->add( mToolSet ); + mToolSet->addActionListener( this ); + + + + mSpeechDeleteButton = new DeleteButtonGL( + mToolSet->getAnchorX() + mToolSet->getWidth(), + mToolSet->getAnchorY() + ( mToolSet->getHeight() - 8 ) / 2, + 8, + 8 ); + mMainPanel->add( mSpeechDeleteButton ); + mSpeechDeleteButton->addActionListener( this ); + + mSpeechDeleteButton->setEnabled( false ); + + mSpeechDeleteButton->setToolTip( "tip_delete_playerSpeech" ); + + + + mActionDeleteButton = new DeleteButtonGL( + mToolSet->getAnchorX() - 8, + mToolSet->getAnchorY() + ( mToolSet->getHeight() - 8 ) / 2, + 8, + 8 ); + mMainPanel->add( mActionDeleteButton ); + mActionDeleteButton->addActionListener( this ); + + mActionDeleteButton->setEnabled( false ); + + mActionDeleteButton->setToolTip( "tip_delete_playerAction" ); + + + + mTipDisplay = new FixedTipDisplay( mToolSet->getAnchorX() + + mToolSet->getWidth() + 20, + 48 ); + mMainPanel->add( mTipDisplay ); + + + mGameStateToEdit = NULL; + + GameState *state = new GameState(); + + setGameStateToEdit( state ); + + mStateDisplay->showGrid( false ); + + + + mSendButton = new SendButtonGL( + 4, + 44, + 16, 16 ); + + mMainPanel->add( mSendButton ); + + mSendButton->addActionListener( this ); + + mSendButton->setToolTip( "tip_playerSend" ); + + + mPracticeStopButton = new SpriteButtonGL( + new Sprite( "practiceStop.tga", true ), + 1, + 300, + 44, + 16, 16 ); + + mMainPanel->add( mPracticeStopButton ); + + mPracticeStopButton->addActionListener( this ); + + mPracticeStopButton->setToolTip( "tip_practiceStop" ); + mPracticeStopButton->setEnabled( false ); + + + + + mFlipBookButton = new SelectableButtonGL( + new Sprite( "flipBook.tga", true ), + 1, + 298, 42, 20, 20 ); + + mMainPanel->add( mFlipBookButton ); + // hide for now, don't let player turn flip books off (except manually + // in settings folder) + mFlipBookButton->setEnabled( false ); + + + char flipBookFound = false; + int readFlipBook = SettingsManager::getIntSetting( "flipBook", + &flipBookFound ); + + char flipBook = false; + + if( flipBookFound && readFlipBook == 1 ) { + flipBook = true; + } + + mFlipBookButton->setSelected( flipBook ); + + mFlipBookButton->addActionListener( this ); + + mFlipBookButton->setToolTip( "tip_flipBook" ); + + + mFlipBookImageNumber = 1; + + File flipBookDir( NULL, "flipBooks" ); + + if( !flipBookDir.exists() ) { + flipBookDir.makeDirectory(); + } + + if( flipBookDir.exists() && flipBookDir.isDirectory() ) { + + int numFiles; + File **childFiles = flipBookDir.getChildFiles( &numFiles ); + + int largestNumber = 0; + + for( int i=0; iisDirectory() ) { + + char *name = childFiles[i]->getFileName(); + + int number; + + int numRead = sscanf( name, "%d", &number ); + + if( numRead == 1 ) { + + if( number > largestNumber ) { + largestNumber = number; + } + } + delete [] name; + } + delete childFiles[i]; + } + delete [] childFiles; + + mFlipBookFolderNumber = largestNumber + 1; + } + else { + mFlipBookFolderNumber = -1; + } + + postConstruction(); + } + + + +PlayerMoveEditor::~PlayerMoveEditor() { + + if( mGameStateToEdit != NULL ) { + delete mGameStateToEdit; + } + + } + + + +void PlayerMoveEditor::setGameStateToEdit( GameState *inGameState ) { + if( mGameStateToEdit != NULL ) { + delete mGameStateToEdit; + } + + mGameStateToEdit = inGameState; + + + mStateDisplay->setState( mGameStateToEdit->copy() ); + + + + if( !mMovesDisabled ) { + // make sure speech toggles match current state + mStateDisplay->mEditingSpeech = + ( mToolSet->getSelected() == playerSpeak ); + } + + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + if( !mMovesDisabled ) { + // action toggles too + mStateDisplay->mEditingAction = + ( mToolSet->getSelected() == playerAct ); + } + + if( mGameStateToEdit->getSelectedActionLength() > 0 ) { + mActionDeleteButton->setEnabled( true ); + } + else { + mActionDeleteButton->setEnabled( false ); + } + + + if( !mMovesDisabled ) { + mToolSet->setMoveAllowed( ! mGameStateToEdit->isObjectZeroFrozen() ); + } + } + + + + +void PlayerMoveEditor::setMovesDisabled( char inDisabled ) { + + char oldDisabled = mMovesDisabled; + + mMovesDisabled = inDisabled; + + mToolSet->setEnabled( !mMovesDisabled ); + + if( !mMovesDisabled && oldDisabled ) { + // just enabled + + // force back to MOVE mode + mToolSet->setSelected( playerMove ); + + mTipDisplay->setTip( + (char*)TranslationManager::translate( "instruction_playerMove" ) ); + + mToolSet->setMoveAllowed( ! mGameStateToEdit->isObjectZeroFrozen() ); + } + else if( mMovesDisabled && !oldDisabled ) { + // just disabled + mSpeechDeleteButton->setEnabled( false ); + mActionDeleteButton->setEnabled( false ); + + // hide any empty actions or speech + mStateDisplay->mEditingSpeech = false; + mStateDisplay->mEditingAction = false; + + // hide instruction tip + mTipDisplay->setTip( NULL ); + } + } + + + +void PlayerMoveEditor::clearNonPlayerSpeech() { + mGameStateToEdit->deleteAllNonPlayerSpeech(); + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + + +extern char practiceMode; +extern char practiceStop; + + +void PlayerMoveEditor::enableSend( char inEnabled ) { + mSendButton->setEnabled( inEnabled ); + + + mPracticeStopButton->setEnabled( inEnabled && practiceMode ); + + /* + if( !inEnabled ) { + mSendButton->resetPressState(); + } + */ + } + + +void PlayerMoveEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mToolSet ) { + // tool change? + + // if speech tool, switch keyboard to main display + + mStateDisplay->mEditingSpeech = + ( mToolSet->getSelected() == playerSpeak ); + + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + char wasEditingAction = mStateDisplay->mEditingAction; + + + mStateDisplay->mEditingAction = + ( mToolSet->getSelected() == playerAct ); + + if( mGameStateToEdit->getSelectedActionLength() > 0 ) { + mActionDeleteButton->setEnabled( true ); + } + else { + mActionDeleteButton->setEnabled( false ); + + // if we're newly editing a blank action again, recenter it + if( !wasEditingAction && mStateDisplay->mEditingAction ) { + AppLog::detail( "Newly editing an action... clearing it\n" ); + + mGameStateToEdit->resetActionAnchor( + mGameStateToEdit->getSelectedObject() ); + + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + } + + + char *instructionTransKey; + + switch( mToolSet->getSelected() ) { + case playerAct: + instructionTransKey = (char*)"instruction_playerAct"; + break; + case playerMove: + instructionTransKey = (char*)"instruction_playerMove"; + break; + case playerSpeak: + instructionTransKey = (char*)"instruction_playerSpeak"; + break; + } + + mTipDisplay->setTip( + (char*)TranslationManager::translate( instructionTransKey ) ); + } + else if( inTarget == mSpeechDeleteButton ) { + mGameStateToEdit->deleteAllCharsFromSelectedSpeech(); + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + + mSpeechDeleteButton->setEnabled( false ); + } + else if( inTarget == mActionDeleteButton ) { + mGameStateToEdit->deleteAllCharsFromSelectedAction(); + mGameStateToEdit->resetActionAnchor( + mGameStateToEdit->getSelectedObject() ); + + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + + mActionDeleteButton->setEnabled( false ); + } + else if( inTarget == mFlipBookButton ) { + // togglem + mFlipBookButton->setSelected( ! mFlipBookButton->getSelected() ); + + // save + int flipBook = 0; + if( mFlipBookButton->getSelected() ) { + flipBook = 1; + } + + SettingsManager::setSetting( "flipBook", flipBook ); + } + else if( inTarget == mSendButton ) { + mSendButton->setEnabled( false ); + fireActionPerformed( this ); + } + else if( inTarget == mPracticeStopButton ) { + // turn practice mode off + practiceStop = true; + mSendButton->setEnabled( false ); + + // end practice player turn + fireActionPerformed( this ); + } + else if( inTarget == mStateDisplay && !mMovesDisabled ) { + + switch( mToolSet->getSelected() ) { + case playerAct: { + + if( mStateDisplay->mLastActionKeyPress ) { + unsigned char key = mStateDisplay->mLastKeyPressed; + + if( key == 127 || key == 8 ) { + // backspace and delete + mGameStateToEdit->deleteCharFromSelectedAction(); + } + else if( (key >= 32 && key <= 126) + || key >= 160 ) { + // allowed range, ascii and extended ascii + mGameStateToEdit->addCharToSelectedAction( + (char)key ); + } + if( mGameStateToEdit->getSelectedActionLength() > 0 ) { + mActionDeleteButton->setEnabled( true ); + } + else { + mActionDeleteButton->setEnabled( false ); + } + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else { + // mouse movement with action tool + // adjust action anchor + + mGameStateToEdit->moveSelectedActionAnchor( + mStateDisplay->mLastPixelClickX, + mStateDisplay->mLastPixelClickY ); + + // no need to generate all new sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + + } + break; + case playerMove: { + if( !mStateDisplay->mLastActionKeyPress ) { + // move even in response to mouse dragging + + int x = mStateDisplay->mLastGridClickX; + int y = mStateDisplay->mLastGridClickY; + + if( ! mGameStateToEdit->mRoom.getWall( x, G - y - 1 ) ) { + + // move whole player object only + mGameStateToEdit->moveSelectedObject( x, y ); + + // no need to generate all new sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + } + + } + break; + case playerSpeak: { + /* + if( mStateDisplay->mLastActionKeyPress ) { + unsigned char key = mStateDisplay->mLastKeyPressed; + + if( key == 127 || key == 8 ) { + mGameStateToEdit->deleteCharFromSelectedSpeech(); + } + else if( key >= 32 && key <= 126 ) { + mGameStateToEdit->addCharToSelectedSpeech( + (char)key ); + } + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + else { + // mouse movement with speech tool + + // ignore it! + } + */ + } + break; + } + + + + if( mToolSet->getSelected() != playerAct ) { + // typing adds to speech even in move mode + if( mStateDisplay->mLastActionKeyPress ) { + unsigned char key = mStateDisplay->mLastKeyPressed; + + if( key == 127 || key == 8 ) { + mGameStateToEdit->deleteCharFromSelectedSpeech(); + } + else if( ( key >= 32 && key <= 126 ) + || key >= 160 ) { + // allowed range, ascii and extended ascii + mGameStateToEdit->addCharToSelectedSpeech( + (char)key ); + } + if( mGameStateToEdit->getSelectedSpeechLength() > 0 ) { + mSpeechDeleteButton->setEnabled( true ); + } + else { + mSpeechDeleteButton->setEnabled( false ); + } + + // no need to redo all sprites + mStateDisplay->updateSpritePositions( + mGameStateToEdit->copy() ); + } + } + + + } + + + } + + + +void PlayerMoveEditor::editorClosing() { + } + + + + +extern int screenWidth; +extern int screenHeight; + + + +static void writeHTMLFile( int inPageNumber, int inMaxPageNumber, + File *inDestDir ) { + + char *pageFileName = autoSprintf( "%05d.html", inPageNumber ); + + File *thisPageFile = + inDestDir->getChildFile( pageFileName ); + + delete [] pageFileName; + + // pure HTML output + + + File templateDir( NULL, "templates" ); + + File *tempFile = + templateDir.getChildFile( "x.html" ); + + char *xText = tempFile->readFileContents(); + delete tempFile; + + tempFile = + templateDir.getChildFile( "prevButton.html" ); + + char *prevText = tempFile->readFileContents(); + delete tempFile; + + tempFile = + templateDir.getChildFile( "nextButton.html" ); + + char *nextText = tempFile->readFileContents(); + delete tempFile; + + tempFile = + templateDir.getChildFile( "preloadNext.html" ); + + char *preloadText = tempFile->readFileContents(); + delete tempFile; + + + char *xNumber = autoSprintf( "%05d", inPageNumber ); + + // default to going back to beginning + char *yNumber = autoSprintf( "%05d", 1 ); + + if( inPageNumber < inMaxPageNumber ) { + // there's a next button + char *nextNumber = + autoSprintf( "%05d", inPageNumber + 1 ); + + char found; + + char *temp = replaceOnce( nextText, "#Y", + nextNumber, + &found ); + delete [] nextText; + nextText = temp; + + temp = replaceOnce( preloadText, "#Y", + nextNumber, + &found ); + delete [] preloadText; + preloadText = temp; + + delete [] yNumber; + yNumber = nextNumber; + } + else { + // no next + delete [] nextText; + nextText = stringDuplicate( "" ); + + delete [] preloadText; + preloadText = stringDuplicate( "" ); + } + + + if( inPageNumber > 1 ) { + // there's a prev button + + char *prevNumber = + autoSprintf( "%05d", inPageNumber - 1 ); + + char found; + + char *temp = replaceOnce( prevText, "#W", + prevNumber, + &found ); + delete [] prevText; + prevText = temp; + + delete [] prevNumber; + } + else { + // no prev + delete [] prevText; + prevText = stringDuplicate( "" ); + } + + SimpleVector targets; + SimpleVector subs; + + targets.push_back( (char*)"#X" ); + subs.push_back( xNumber ); + + targets.push_back( (char*)"#Y" ); + subs.push_back( yNumber ); + + targets.push_back( (char*)"#PREV" ); + subs.push_back( prevText ); + + targets.push_back( (char*)"#NEXT" ); + subs.push_back( nextText ); + + targets.push_back( (char*)"#PRELOAD" ); + subs.push_back( preloadText ); + + + + char *finalText = replaceTargetListWithSubstituteList( + xText, &targets, &subs ); + + + delete [] xText; + delete [] prevText; + delete [] nextText; + delete [] preloadText; + delete [] yNumber; + delete [] xNumber; + + thisPageFile->writeToFile( finalText ); + + delete thisPageFile; + delete [] finalText; + } + + + +// to map screen coordinates to take same screen shot regardless of window +// resolution +extern GUITranslatorGL *guiTranslator; + +extern int pixelZoomFactor; + + + +void PlayerMoveEditor::saveFlipBookImage() { + + if( mFlipBookButton->getSelected() && + mFlipBookFolderNumber > 0 ) { + + + + File flipBookDir( NULL, "flipBooks" ); + + + if( flipBookDir.exists() && flipBookDir.isDirectory() ) { + char *thisBookName = autoSprintf( "%05d", mFlipBookFolderNumber ); + + File *thisBookDir = flipBookDir.getChildFile( thisBookName ); + + delete [] thisBookName; + + + if( ! thisBookDir->exists() ) { + thisBookDir->makeDirectory(); + + // copy PHP templates into place + + File templateDir( NULL, "templates" ); + + const char *namesToCopy[6] = { "header.php", "footer.php", + "index.php", "index.html", + "next.png", "prev.png" }; + + for( int i=0; i<6; i++ ) { + char success = false; + + File *templateFile = + templateDir.getChildFile( namesToCopy[i] ); + + if( templateFile->exists() ) { + + File *destFile = + thisBookDir->getChildFile( namesToCopy[i] ); + + if( destFile != NULL ) { + templateFile->copy( destFile ); + success = true; + delete destFile; + } + } + + if( !success ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to copy Flip Book template file %s\n", + namesToCopy[i] ); + } + + + delete templateFile; + } + + + } + + + if( thisBookDir->exists() && thisBookDir->isDirectory() ) { + + + + + // NOTE: this is a bit of a hack + // the GUI coordinate trans only can map from screen to + // GUI space. We have GUI space coords, and we want to + // map to screen space to trim our screen shot. + + // SO, hack is just too loop over all possible values, + // translating each one until we find a match. + + // This is actually fast enough (especially compared to the + // image conversion/processing), even though it seems wasteful + // Not as wasteful (and bug-prone) as me trying to write + // inverse coordinate translation functions. + + int w = screenWidth; + int h = screenHeight; + + int yBottom = (int)mStateDisplay->getAnchorY(); + + int hSkip = 0; + while( guiTranslator->translateY( screenHeight - hSkip ) + < yBottom ) { + hSkip ++; + } + + if( pixelZoomFactor > 1 && pixelZoomFactor % 2 != 0 ) { + hSkip -= 1; + } + + int yTop = yBottom + (int)mStateDisplay->getHeight(); + + + int hTop = hSkip; + + while( guiTranslator->translateY( screenHeight - hTop ) + < yTop ) { + hTop ++; + } + + if( pixelZoomFactor > 1 && pixelZoomFactor % 2 != 0 ) { + hTop -= 1; + } + + h = hTop - hSkip; + + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "H to grab = %d, skipping %d\n", h, hSkip ); + + + + int xLeft = 0; + + int wSkip = 0; + while( guiTranslator->translateX( wSkip ) < xLeft ) { + wSkip ++; + } + int xRight = gameWidth; + + int wRight = wSkip; + + while( guiTranslator->translateX( wRight ) < xRight ) { + wRight ++; + } + + if( pixelZoomFactor > 1 && pixelZoomFactor % 2 != 0 ) { + wRight -= 1; + } + + w = wRight - wSkip; + + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "W to grab = %d, skipping %d\n", w, wSkip ); + + + unsigned char *rgbBytes = new unsigned char[ w * h * 3 ]; + + // w and h might not be multiples of 4 + int oldAlignment; + glGetIntegerv( GL_PACK_ALIGNMENT, &oldAlignment ); + //printf( "Old alignment = %d\n", oldAlignment ); + + glPixelStorei( GL_PACK_ALIGNMENT, 1 ); + + glReadPixels( wSkip, hSkip, w, h, + GL_RGB, GL_UNSIGNED_BYTE, rgbBytes ); + + glPixelStorei( GL_PACK_ALIGNMENT, oldAlignment ); + + + int baseWidth = 320; + int baseHeight = 208; + + // pixel doubling + int pageMultiple = 2; + int pageWidth = baseWidth * pageMultiple; + int pageHeight = baseHeight * pageMultiple; + + + int screenMultiple; + + char resizeFailure = false; + + + // make sure w and h are even multiples of the base + if( h % baseHeight != 0 || + w % baseWidth != 0 ) { + + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Trimmed screen is not an even multiple of flip" + " book base size: base=%dx%d, trimmed screen=" + " %dx%d\n", baseWidth, baseHeight, w, h ); + + resizeFailure = true; + } + else { + screenMultiple = h / baseHeight; + + if( screenMultiple != w / baseWidth ) { + AppLog::error( + "Error: flip book output: multiple for screen" + " widht not equal to multiple for height" ); + + resizeFailure = true; + } + } + + + if( resizeFailure ) { + // default to actual size in this case, instead of + // trying to shrink + pageWidth = w; + pageHeight = h; + + baseWidth = w; + baseHeight = h; + + pageMultiple = 1; + screenMultiple = 1; + } + + + Image pageImage( pageWidth, pageHeight, 3, false ); + + double *channels[3]; + int c; + for( c=0; c<3; c++ ) { + channels[c] = pageImage.getChannel( c ); + } + + // image of screen is upside down + int outputRow = 0; + for( int y=pageHeight-1; y>=0; y-- ) { + int screenY = (y / pageMultiple) * screenMultiple; + + for( int x=0; xgetChildFile( "frameList.php" ); + + SimpleVector listAccume; + + listAccume.appendElementString( + "" ); + + char *listString = listAccume.getElementString(); + + listFile->writeToFile( listString ); + delete [] listString; + + delete listFile; + + + + int thisImageNumber = mFlipBookImageNumber; + + + mFlipBookImageNumber++; + + + + File *thisBookImagesDir = + thisBookDir->getChildFile( "images" ); + + if( ! thisBookImagesDir->exists() ) { + thisBookImagesDir->makeDirectory(); + } + + if( thisBookImagesDir->exists() && + thisBookImagesDir->isDirectory() ) { + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Flipbook output %s\n", thisPNGName ); + + File *thisPNGFile = + thisBookImagesDir->getChildFile( thisPNGName ); + + + FileOutputStream pageOutput( thisPNGFile ); + + + PNGImageConverter png; + + double t = Time::getCurrentTime(); + + png.formatImage( &pageImage, &pageOutput ); + + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, "Converter took %f seconds\n", + Time::getCurrentTime() - t ); + + + delete thisPNGFile; + + + // now HTML templates + + // new one + writeHTMLFile( thisImageNumber, + thisImageNumber, + thisBookImagesDir ); + + if( thisImageNumber > 1 ) { + // redo last one, since there's a "next" now + writeHTMLFile( thisImageNumber - 1, + thisImageNumber, + thisBookImagesDir ); + } + + } + delete [] thisPNGName; + delete [] thisPageName; + + delete thisBookImagesDir; + } + + + delete thisBookDir; + } + + + + } + + } + diff --git a/gameSource/PlayerMoveEditor.h b/gameSource/PlayerMoveEditor.h new file mode 100644 index 0000000..3523631 --- /dev/null +++ b/gameSource/PlayerMoveEditor.h @@ -0,0 +1,79 @@ +#ifndef PLAYER_MOVE_EDITOR_INCLUDED +#define PLAYER_MOVE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "GameState.h" +#include "GameStateDisplay.h" +#include "MoveToolSet.h" +#include "FixedTipDisplay.h" + + +class PlayerMoveEditor : public Editor { + + public: + + PlayerMoveEditor( ScreenGL *inScreen ); + + ~PlayerMoveEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + + void setGameStateToEdit( GameState *inGameState ); + + GameState *mGameStateToEdit; + + + void setMovesDisabled( char inDisabled ); + + + // clears all but player speech + void clearNonPlayerSpeech(); + + + void enableSend( char inEnabled ); + + + // has no effect if flip book turned off + void saveFlipBookImage(); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + + GameStateDisplay *mStateDisplay; + + MoveToolSet *mToolSet; + + DeleteButtonGL *mSpeechDeleteButton; + DeleteButtonGL *mActionDeleteButton; + + + SendButtonGL *mSendButton; + SpriteButtonGL *mPracticeStopButton; + + + FixedTipDisplay *mTipDisplay; + + + SelectableButtonGL *mFlipBookButton; + + + char mMovesDisabled; + + + // updated once per play session + int mFlipBookFolderNumber; + + // starts at 1 for each play session + int mFlipBookImageNumber; + + }; + +#endif diff --git a/gameSource/PressActionGUIPanelGL.cpp b/gameSource/PressActionGUIPanelGL.cpp new file mode 100644 index 0000000..ff6ec41 --- /dev/null +++ b/gameSource/PressActionGUIPanelGL.cpp @@ -0,0 +1,22 @@ +#include "PressActionGUIPanelGL.h" + + +PressActionGUIPanelGL::PressActionGUIPanelGL( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Color *inColor ) + : GUIPanelGL( inAnchorX, inAnchorY, inWidth, + inHeight, inColor ) { + + } + + + +void PressActionGUIPanelGL::mouseReleased( double inX, double inY ) { + GUIPanelGL::mouseReleased( inX, inY ); + + if( isInside( inX, inY ) ) { + + fireActionPerformed( this ); + } + } + diff --git a/gameSource/PressActionGUIPanelGL.h b/gameSource/PressActionGUIPanelGL.h new file mode 100644 index 0000000..e50a80e --- /dev/null +++ b/gameSource/PressActionGUIPanelGL.h @@ -0,0 +1,25 @@ + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" + +#include "minorGems/ui/event/ActionListenerList.h" + + +// panel that fires an action when mouse pressed on it +// can be used to hold focus when mouse pressed outside a component of interest +// (but hits containing panel) +class PressActionGUIPanelGL : public GUIPanelGL, public ActionListenerList { + public: + + PressActionGUIPanelGL( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Color *inColor ); + + + virtual ~PressActionGUIPanelGL() { + } + + + // override to fire action + virtual void mouseReleased( double inX, double inY ); + + }; diff --git a/gameSource/ResourcePicker.cpp b/gameSource/ResourcePicker.cpp new file mode 100644 index 0000000..feac37d --- /dev/null +++ b/gameSource/ResourcePicker.cpp @@ -0,0 +1,804 @@ +#ifndef RESOURCE_PICKER_IMPLEMENTATION_INCLUDED +#define RESOURCE_PICKER_IMPLEMENTATION_INCLUDED + + +#include "ResourcePicker.h" +#include "resourceDatabase.h" +#include "resourceManager.h" +#include "usageDatabase.h" +#include "speechHints.h" +#include "Room.h" +#include "imageCache.h" + + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + + +#include +#include +#include + + +extern TextGL *largeTextFixed; + + +template +ResourcePicker::ResourcePicker( + double inAnchorX, double inAnchorY, + SimpleVector *inStack ) + : BorderPanel( inAnchorX, inAnchorY, 70, 93, + new Color( 0, 0, 0, 1 ), + new Color( 0.35, 0.35, 0.35, 1 ), + 1 ), + mNumResourcesOnScreen( 0 ), + mSearchResultOffset( 0 ), + mNumSearchResults( 0 ), + mStackMode( false ), + mStack( inStack ) { + + double tileSpace = P; + + // room for button borders + double buttonSpace = tileSpace + 4; + + // use same separation for both x and y + double buttonSeparation = + ( mWidth - buttonSpace * mGridW ) / (mGridW + 1); + + + // put left and right at very bottom + mLeftButton = new LeftButtonGL( mAnchorX + buttonSeparation, + mAnchorY + buttonSeparation, + 8, 8 ); + add( mLeftButton ); + mLeftButton->setEnabled( false ); + mLeftButton->addActionListener( this ); + + + mRightButton = new RightButtonGL( mAnchorX + mWidth - buttonSeparation - 8, + mAnchorY + buttonSeparation, + 8, 8 ); + add( mRightButton ); + mRightButton->setEnabled( false ); + mRightButton->addActionListener( this ); + + + // centered above picker + mStackSearchButton = new StackSearchButtonGL( mAnchorX + (mWidth-8)/3 - 1, + mAnchorY + 1, + 8, 8 ); + add( mStackSearchButton ); + mStackSearchButton->addActionListener( this ); + + + double baseY = mAnchorY + buttonSeparation + 8; + + + + double topButtonY = baseY + buttonSeparation + + mGridH * ( buttonSpace + buttonSeparation ) - buttonSpace; + + + for( int y=0; ysetEnabled( false ); + + mButtonGrid[y][x]->addActionListener( this ); + } + } + + mLastActionFromPress = false; + + + + int fieldHeight = 8; + int fieldWidth = 8 * 8; + + const char *defaultSearch = ""; + + double fieldAnchorY = topButtonY + buttonSpace + 1; + + mSearchField = new TextFieldGL( mAnchorX + 2, + fieldAnchorY, + fieldWidth, + fieldHeight, + 1, + defaultSearch, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 8, + false ); + add( mSearchField ); + + mSearchField->setFocus( false ); + //mSearchField->lockFocus( true ); + + mSearchField->setCursorPosition( strlen( defaultSearch ) ); + + mSearchField->addActionListener( this ); + + + + + mSelectedResourceButton = new DraggableTwoSpriteButtonGL( + NULL, + NULL, + 1, + mAnchorX + mWidth - buttonSpace - 2, + mAnchorY + mHeight - buttonSpace - 2, + buttonSpace, + buttonSpace ); + + add( mSelectedResourceButton ); + mSelectedResourceButton->setEnabled( true ); + mSelectedResourceButton->addActionListener( this ); + + refreshSelectedResourceSprite(); + + + + mDeleteButton = new DeleteButtonGL( + mSelectedResourceButton->getAnchorX() - 8, + mAnchorY + mHeight - 10, + 8, 8 ); + add( mDeleteButton ); + mDeleteButton->addActionListener( this ); + mDeleteButton->setEnabled( false ); + + // concatonate + char *tipKey = autoSprintf( "tip_delete_%s", + ResourceType::getResourceType() ); + + mDeleteButton->setToolTip( tipKey ); + delete [] tipKey; + + + + EightPixelLabel *tileLabel = + new EightPixelLabel( mAnchorX + 2, + mAnchorY + mHeight - 8 - 2, + (char*)( ResourceType::getResourceType() ) ); + add( tileLabel ); + + + mSearchLabel = new EightPixelLabel( mAnchorX + 2, + fieldAnchorY + + fieldHeight + 1, + "search" ); + add( mSearchLabel ); + + mStackLabel = new EightPixelLabel( mAnchorX + 2, + fieldAnchorY + + fieldHeight + 1, + "stack" ); + } + + + + +template +ResourcePicker::~ResourcePicker() { + + if( !contains( mSearchLabel ) ) { + delete mSearchLabel; + } + if( !contains( mSearchField ) ) { + delete mSearchField; + } + if( !contains( mStackLabel ) ) { + delete mStackLabel; + } + } + + + +template +ResourceType ResourcePicker::getSelectedResource() { + return mSelectedResource; + } + + + +template +uniqueID ResourcePicker::getSelectedResourceID() { + return mSelectedResource.getUniqueID(); + } + + + +template +void ResourcePicker::refreshSelectedResourceSprite() { + Sprite *backgroundSprite = getButtonGridBackground(); + + char useTrans = false; + if( backgroundSprite != NULL ) { + useTrans = true; + } + + + mSelectedResourceButton->setFrontSprite( + mSelectedResource.getSprite( useTrans ) ); + + mSelectedResourceButton->setSprite( backgroundSprite ); + + char *tip = mSelectedResource.getName(); + mSelectedResourceButton->setToolTip( tip, false ); + delete [] tip; + } + + + +template +void ResourcePicker::recheckDeletable() { + if( equal( mSelectedResource.getUniqueID(), + ResourceType::getDefaultResource().getUniqueID() ) ) { + + // can't delete blank + mDeleteButton->setEnabled( false ); + } + else if( isUsed( mSelectedResource.getUniqueID() ) ) { + uniqueID user = getUser( mSelectedResource.getUniqueID() ); + + char *userName = getResourceName( user ); + + char *tip; + + char *resourceName = mSelectedResource.getName(); + + if( userName != NULL ) { + + tip = autoSprintf( "%s (used in %s)", + resourceName, + userName ); + } + else { + tip = autoSprintf( "%s (used in current edit)", + resourceName ); + } + delete [] resourceName; + + + mSelectedResourceButton->setToolTip( tip, false ); + delete [] tip; + + // can't delete a resource used elsewhere + mDeleteButton->setEnabled( false ); + } + else { + // not default, and not used anywhere. Deletable! + mDeleteButton->setEnabled( true ); + } + } + + + +template +void ResourcePicker::setSelectedResource( + ResourceType inResource, char inForceResultRefresh, char inFireEvent ) { + + if( !equal( mSelectedResource.getUniqueID(), inResource.getUniqueID() ) ) { + + // a change + + mSelectedResource = inResource; + + refreshSelectedResourceSprite(); + + + // remove from lower in stack + uniqueID id = mSelectedResource.getUniqueID(); + char found = false; + int stackSize = mStack->size(); + for( int i=0; igetElement( i ) ) ) ) { + mStack->deleteElement( i ); + found = true; + } + } + mStack->push_back( id ); + + if( mStack->size() > 256 ) { + // size limit reached + + // delete oldest + mStack->deleteElement( 0 ); + } + + + recheckDeletable(); + + + if( mStackMode ) { + // back to top whenever top changes + mSearchResultOffset = 0; + } + + + if( inForceResultRefresh ) { + newSearch( true ); + } + else if( mStackMode ) { + if( found ) { + // just stack order changed + getFreshResults(); + } + else { + // something new added to stack... maybe a size change + newSearch( false ); + } + } + + + if( inFireEvent ) { + mLastActionFromPress = false; + fireActionPerformed( this ); + } + } + + + } + + + +template +void ResourcePicker::forceNewSearch() { + newSearch( false ); + } + + + +template +void ResourcePicker::newSearch( char inPreservePageOffset, + char inMoveBackward ) { + + char *search = stringDuplicate( mSearchField->getText() ); + + int numResults; + + if( !mStackMode ) { + numResults = countSearchResults( ResourceType::getResourceType(), + search ); + } + else { + numResults = countStackResults(); + } + + //printf( "New resource search, %d results\n", numResults ); + + delete [] search; + + + int oldOffset = mSearchResultOffset; + + + mNumSearchResults = numResults; + mSearchResultOffset = 0; + + + if( inPreservePageOffset ) { + if( oldOffset < numResults ) { + // still in range + mSearchResultOffset = oldOffset; + } + } + + + if( numResults > mNumResourcesDisplayedMax ) { + // scroll + numResults = mNumResourcesDisplayedMax; + mLeftButton->setEnabled( true ); + mRightButton->setEnabled( true ); + } + else { + // only one page + mLeftButton->setEnabled( false ); + mRightButton->setEnabled( false ); + } + + getFreshResults( inMoveBackward ); + } + + + + +template +Sprite *ResourcePicker::getButtonGridBackground() { + return NULL; + } + + + + + + +template +void ResourcePicker::getFreshResults( char inMoveBackward ) { + uniqueID ids[mNumResourcesDisplayedMax]; + + + // clear old sprites + int i; + for( i=0; isetEnabled( false ); + } + + + char *search = stringDuplicate( mSearchField->getText() ); + + + //printf( "Fresh results with offset %d from %d results\n", + // mSearchResultOffset, mNumSearchResults ); + + if( !mStackMode ) { + mNumResourcesOnScreen = getSearchResults( + ResourceType::getResourceType(), + search, + mSearchResultOffset, + mNumResourcesDisplayedMax, + ids ); + + + } + else { + mNumResourcesOnScreen = getStackResults( mSearchResultOffset, + mNumResourcesDisplayedMax, + ids ); + } + + delete [] search; + + Sprite *backgroundSprite = getButtonGridBackground(); + + char useTrans = false; + if( backgroundSprite != NULL ) { + useTrans = true; + + // delete this test sprite, because we need to add a copy + // to every button below + delete backgroundSprite; + } + + + char badResultCount = 0; + + for( i=0; isetEnabled( true ); + mButtonGrid[y][x]->setFrontSprite( r.getSprite( useTrans ) ); + mButtonGrid[y][x]->setSprite( getButtonGridBackground() ); + + char *tip = r.getName(); + mButtonGrid[y][x]->setToolTip( tip, false ); + delete [] tip; + + if( ! equal( ids[i], r.getUniqueID() ) ) { + // this resource failed to load (bogus search result in index, + // and it will be removed by resourceManager) + + + // it will show up as a "default" resource with blank sprite, + // even if the current search does not match the word "default", + // which is confusing. + badResultCount++; + + // abandon this search and start a brand new one, attempting + // to maintain the page offset + + // however, finish loading rest of resources in this batch + // in case it contains multiple bad ones (they are only removed + // by resourceManager when they are loaded), instead of removing + // them one-by-one with several newSearches (slow) + } + } + + + if( badResultCount > 0 ) { + + if( inMoveBackward ) { + // when moving backward through results, + // the removal of bad results can cause the left button + // to appear to have no effect + + // account for this + mNumSearchResults -= badResultCount; + mSearchResultOffset -= badResultCount; + lowerBoundSearchOffset(); + } + + newSearch( true, inMoveBackward ); + } + + } + + + + +template +void ResourcePicker::lowerBoundSearchOffset() { + if( mSearchResultOffset < 0 ) { + // roll back to last page + int numOnLastPage = mNumSearchResults % mNumResourcesDisplayedMax; + + mSearchResultOffset = mNumSearchResults - numOnLastPage; + if( numOnLastPage == 0 ) { + // an even number of full pages + + // show last page + mSearchResultOffset = + mSearchResultOffset - mNumResourcesDisplayedMax; + } + } + } + + + +template +void ResourcePicker::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mSearchField ) { + // search changed + newSearch(); + } + else if( inTarget == mDeleteButton ) { + // confirmed delete! + + uniqueID id = mSelectedResource.getUniqueID(); + + deleteResource( ResourceType::getResourceType(), id ); + + removeUsages( id ); + + // delete from stack, too + char found = false; + int stackSize = mStack->size(); + // walk backward... most likely at top of stack (right?) + for( int i=stackSize-1; i>=0 && !found; i-- ) { + if( equal( id, *( mStack->getElement( i ) ) ) ) { + AppLog::detail( "Found element to delete in stack\n" ); + mStack->deleteElement( i ); + found = true; + } + } + + // delete from image cache, too + clearCachedImages( id ); + + // also hint no longer needed + clearSpeechHint( id ); + + + + // refresh results (in case they show the deleted tile) + newSearch( true ); + + if( !mStackMode || mStack->size() == 0 ) { + // set to default + setSelectedResource( ResourceType::getDefaultResource() ); + } + else { + // take next top of stack + ResourceType r( *( mStack->getElement( mStack->size() - 1 ) ) ); + setSelectedResource( r ); + } + } + else if( inTarget == mLeftButton ) { + mSearchResultOffset -= mNumResourcesDisplayedMax; + lowerBoundSearchOffset(); + getFreshResults( true ); + } + else if( inTarget == mRightButton ) { + mSearchResultOffset += mNumResourcesDisplayedMax; + if( mSearchResultOffset > mNumSearchResults - 1 ) { + // wrap back around + mSearchResultOffset = 0; + } + getFreshResults(); + } + else if( inTarget == mStackSearchButton ) { + mStackMode = mStackSearchButton->getState(); + + + if( mStackMode ) { + remove( mSearchField ); + remove( mSearchLabel ); + + add( mStackLabel ); + } + else { + remove( mStackLabel ); + + add( mSearchLabel ); + add( mSearchField ); + } + + newSearch(); + } + else if( inTarget == mSelectedResourceButton ) { + if( mSelectedResourceButton->mLastActionFromPress ) { + // event from a press.... start drag and drop? + mLastActionFromPress = true; + mCurrentDraggedResource = mSelectedResource; + + fireActionPerformed( this ); + } + } + else { + // consider a button press + char found = false; + for( int i=0; imLastActionFromPress ) { + + + + // don't let it fire an event... + // (because it only fires on item change) + setSelectedResource( mResourcesDisplayed[ i ], false, + false ); + // ...we fire our own, even if there's no change + mLastActionFromPress = false; + fireActionPerformed( this ); + + + /* + if( mStackMode ) { + // show top of stack again + mSearchResultOffset = 0; + getFreshResults(); + } + */ + } + else { + // event from a press.... start drag and drop? + mLastActionFromPress = true; + mCurrentDraggedResource = mResourcesDisplayed[ i ]; + + fireActionPerformed( this ); + + } + + } + } + } + + + } + + + +template +void ResourcePicker::setState( ResourceType inSelectedResource, + char *inSearchString, + int inResultOffset, + char inStackMode ) { + mSearchField->setText( inSearchString ); + mSearchResultOffset = inResultOffset; + + char stackModeChange = ( mStackMode != inStackMode ); + mStackMode = inStackMode; + mStackSearchButton->setState( mStackMode ); + + if( stackModeChange ) { + if( mStackMode ) { + remove( mSearchField ); + remove( mSearchLabel ); + + add( mStackLabel ); + } + else { + remove( mStackLabel ); + + add( mSearchLabel ); + add( mSearchField ); + } + } + + setSelectedResource( inSelectedResource ); + + // always do new search after clone (in case selected resource doesn't + // change, so no new search would be triggered) + newSearch( true ); + } + + + +template +void ResourcePicker::cloneState( + ResourcePicker *inOtherPicker ) { + + inOtherPicker->setState( mSelectedResource, + mSearchField->getText(), + mSearchResultOffset, + mStackMode ); + } + + + + + +template +int ResourcePicker::countStackResults() { + return mStack->size(); + } + + + +template +int ResourcePicker::getStackResults( int inNumToSkip, + int inNumToGet, + uniqueID *outIDs ) { + int numGotten = 0; + + int stackSize = mStack->size(); + + int end = inNumToSkip + inNumToGet; + if( end > stackSize ) { + end = stackSize; + } + + for( int i=inNumToSkip; igetElement( stackSize - i - 1 ) ); + numGotten++; + } + + return numGotten; + } + + + +template +char ResourcePicker::wasLastActionFromPress() { + return mLastActionFromPress; + } + + + +template +ResourceType ResourcePicker::getDraggedResource() { + + return mCurrentDraggedResource; + } + + + +template +char ResourcePicker::isSearchFieldFocused() { + return mSearchField->isFocused(); + } + + + + +#endif diff --git a/gameSource/ResourcePicker.h b/gameSource/ResourcePicker.h new file mode 100644 index 0000000..ed4ddfe --- /dev/null +++ b/gameSource/ResourcePicker.h @@ -0,0 +1,172 @@ +#ifndef RESOURCE_PICKER_INCLUDED +#define RESOURCE_PICKER_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIContainerGL.h" +#include "minorGems/ui/event/ActionListenerList.h" +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + +#include "BorderPanel.h" +#include "Sprite.h" +#include "buttons.h" +#include "uniqueID.h" +#include "labels.h" + + +template +class ResourcePicker : public BorderPanel, public ActionListener, + public ActionListenerList { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * @param inStack pointer to the stack shared by this picker. + * Must be destroyed externally. + * + * Sets its own width and height automatically. + */ + ResourcePicker( double inAnchorX, double inAnchorY, + SimpleVector *inStack ); + + + + virtual ~ResourcePicker(); + + + + ResourceType getSelectedResource(); + uniqueID getSelectedResourceID(); + + void setSelectedResource( ResourceType inResource, + char inForceResultRefresh = false, + char inFireEvent = true ); + + + void forceNewSearch(); + + + // used to check if DragAndDrop should be started by an action + char wasLastActionFromPress(); + + ResourceType getDraggedResource(); + + + + char isSearchFieldFocused(); + + + // Forces this picker to recheck if selected resource is deleteable + // (should be called after a usage database change) + void recheckDeletable(); + + + // action listener + virtual void actionPerformed( GUIComponent *inTarget ); + + + + // copy the state from this picker onto another picker + virtual void cloneState( ResourcePicker *inOtherPicker ); + + + protected: + + + // force the state of this picker + void setState( ResourceType inSelectedResource, + char *inSearchString, + int inResultOffset, + char inStackMode ); + + + + static const int mNumResourcesDisplayedMax = 6; + + ResourceType mResourcesDisplayed[mNumResourcesDisplayedMax]; + + //Sprite *mDisplaySprites[mNumResourcesDisplayedMax]; + + static const int mGridW = 3; + static const int mGridH = 2; + + DraggableTwoSpriteButtonGL *mButtonGrid[mGridH][mGridW]; + + char mLastActionFromPress; + + ResourceType mCurrentDraggedResource; + + + // defaults to NULL + // can be overridden by subclasses to provide a background + // behind transparent resources + // if non-NULL, will be destroyed by caller + virtual Sprite *getButtonGridBackground(); + + + + ResourceType mSelectedResource; + + DraggableTwoSpriteButtonGL *mSelectedResourceButton; + + DeleteButtonGL *mDeleteButton; + + + LeftButtonGL *mLeftButton; + RightButtonGL *mRightButton; + + + StackSearchButtonGL *mStackSearchButton; + + + int mNumResourcesOnScreen; + + int mSearchResultOffset; + int mNumSearchResults; + + TextFieldGL *mSearchField; + + EightPixelLabel *mSearchLabel; + EightPixelLabel *mStackLabel; + + + + void newSearch( char inPreservePageOffset=false, + char inMoveBackward=false ); + void getFreshResults( char inMoveBackward=false ); + + void refreshSelectedResourceSprite(); + + void lowerBoundSearchOffset(); + + + char mStackMode; + + // pointer passed in from outside (so that two pickers can share + // the same stack) + SimpleVector *mStack; + + + int countStackResults(); + + + // caller allocates spaces for inNumToGet and passes pointer as outIDs + // returns the number returned + int getStackResults( int inNumToSkip, + int inNumToGet, + uniqueID *outIDs ); + }; + + + +#endif + + + diff --git a/gameSource/Room.cpp b/gameSource/Room.cpp new file mode 100644 index 0000000..da45df5 --- /dev/null +++ b/gameSource/Room.cpp @@ -0,0 +1,400 @@ +#include "Room.h" +#include "resourceManager.h" +#include "usageDatabase.h" + +#include "uniqueID.h" + +#include "imageCache.h" +#include "packSaver.h" + + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + + + + + +// static inits +Tile *Room::sBlankTile = NULL; +Room *Room::sBlankRoom = NULL; + +void Room::staticInit() { + sBlankTile = new Tile(); + // ensure that blank tile is saved at least once + sBlankTile->finishEdit(); + + sBlankRoom = new Room(); + // save once + sBlankRoom->finishEdit(); + } + +void Room::staticFree() { + delete sBlankTile; + delete sBlankRoom; + } + + + +void Room::setupDefault() { + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + + uniqueID blankID = sBlankTile->getUniqueID(); + + int x; + int y; + for( y=0; y 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Room name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + + +uniqueID Room::getTile( int inX, int inY ) { + return mTiles[inY][inX]; + } + + +char Room::getWall( int inX, int inY ) { + return mWallFlags[inY][inX]; + } + + +char *Room::getRoomName() { + return stringDuplicate( mName ); + } + + + +// finishes the edit, generates a new unique ID, saves result +void Room::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( !equal( oldID, mID ) || + ! resourceExists( "room", mID ) ) { + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "room", mID, mName, bytes, numBytes ); + delete [] bytes; + + + // track usages + for( int y=0; ygetChannel( 0 ), + fullImage->getChannel( 1 ), + fullImage->getChannel( 2 ) }; + + + + for( int ty=0; tygetChannel( 0 ), + tImage->getChannel( 1 ), + tImage->getChannel( 2 ) }; + + // compute average of all tile pixels + + for( int c=0; c<3; c++ ) { + // sum first + for( int i=0; i buffer; + + int y, x; + + for( y=0; y= G*G ) { + + memcpy( (void *)mWallFlags, inBytes, G*G ); + + inBytes = &( inBytes[ G*G ] ); + inLength -= G*G; + } + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, inBytes, inLength ); + } + } + + + +void Room::makeUniqueID() { + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + mID = ::makeUniqueID( bytes, numBytes ); + delete [] bytes; + } diff --git a/gameSource/Room.h b/gameSource/Room.h new file mode 100644 index 0000000..edd9b61 --- /dev/null +++ b/gameSource/Room.h @@ -0,0 +1,102 @@ +#ifndef ROOM_INCLUDED +#define ROOM_INCLUDED + + +#include "color.h" +#include "common.h" +#include "uniqueID.h" +#include "Sprite.h" + +#include "Tile.h" + + + +class Room { + public: + + // room filled with blank tiles and default name + Room(); + + + // room loaded from file + Room( uniqueID inID ); + + + // changes the tile in a given grid location + void editRoomTile( int inX, int inY, uniqueID inTileID ); + + void editRoomWall( int inX, int inY, char inIsWall ); + + // name has at most 10 chars + void editRoomName( const char *inName ); + + // gets ID of tile on grid + uniqueID getTile( int inX, int inY ); + + char getWall( int inX, int inY ); + + char *getRoomName(); + + + + // finishes the edit, generates a new unique ID, saves result + void finishEdit( char inGenerateNewID=true ); + + + // recursively saves to current resource pack + void saveToPack(); + + + // image version of getSprite, never fetched from cache + Image *getImage(); + + + // implements ResourceType functions as needed by ResourcePicker + uniqueID getUniqueID(); + Sprite *getSprite( char inUseTrans=false, char inCacheOK=true ); + static const char *getResourceType(); + static Room getDefaultResource(); + + char *getName() { + return getRoomName(); + } + + + + // blank tile used as "edge" in a room + static Tile *sBlankTile; + + // blank room + static Room *sBlankRoom; + + static void staticInit(); + static void staticFree(); + + + protected: + void setupDefault(); + + + // result destroyed by caller + unsigned char *makeBytes( int *outLength ); + + void readFromBytes( unsigned char *inBytes, int inLength ); + + + void makeUniqueID(); + + + // all tiles are exactly one grid square in size + uniqueID mTiles[G][G]; + + char mWallFlags[G][G]; + + char mName[11]; + + uniqueID mID; + + }; + + + +#endif diff --git a/gameSource/RoomEditor.cpp b/gameSource/RoomEditor.cpp new file mode 100644 index 0000000..81de84c --- /dev/null +++ b/gameSource/RoomEditor.cpp @@ -0,0 +1,775 @@ +#include "RoomEditor.h" +#include "RoomPicker.h" +#include "TilePicker.h" +#include "BorderPanel.h" +#include "GridOverlay.h" +#include "labels.h" + +#include "minorGems/util/log/AppLog.h" + + +#include + + +extern TilePicker *mainTilePicker; +extern RoomPicker *mainRoomPicker; + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + + + + +template <> +void SizeLimitedVector::deleteElementOfType( + Room inElement ) { + // no delete necessary + } + + + + +RoomEditor::RoomEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mStateDisplay( NULL ), + mUndoStack( MAX_UNDOS, false ), + // gen a fake ID + mCurrentWorkingStateID( makeUniqueID( (unsigned char*)"currRoom", + strlen( "currRoom" ) ) ) { + + AppLog::info( "Constructing room editor\n" ); + + mCloseButton->setToolTip( "tip_closeEdit_room" ); + + /* + LabelGL *titleLabel = new LabelGL( 0.75, 0.5, 0.25, + 0.1, "Room Editor", largeText ); + + + + mSidePanel->add( titleLabel ); + */ + + mSidePanel->add( mainTilePicker ); + + mainTilePicker->addActionListener( this ); + + + mSidePanel->add( mainRoomPicker ); + + mainRoomPicker->addActionListener( this ); + + + mEditTileButton = + new EditButtonGL( + mainTilePicker->getAnchorX() - 1, + mainTilePicker->getAnchorY() + mainTilePicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mEditTileButton ); + + mEditTileButton->addActionListener( this ); + + mEditTileButton->setToolTip( "tip_edit_tile" ); + + /* + // 1-pixel wide white border + // inner panel to provide a 1-pixel wide black gap + + BorderPanel *workingColorBorderPanel = + new BorderPanel( 256, 206, 48, 14, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + 1 ); + + + // smaller to leave black gap + GUIPanelGL *workingColorPanel = new GUIPanelGL( 258, 208, 44, 10, + mWorkingColor ); + + mSidePanel->add( workingColorBorderPanel ); + workingColorBorderPanel->add( workingColorPanel ); + + + mAddButton = new AddButtonGL( 272, 184, 16, 16 ); + mSidePanel->add( mAddButton ); + mAddButton->addActionListener( this ); + + */ + + + double offset = P; + + double buttonSize = P; + + + for( int y=0; yadd( mButtonGrid[y][x] ); + + mButtonGrid[y][x]->addActionListener( this ); + } + } + + + + + // no stamp tool here + mToolSet = new DrawToolSet( 2, 42, "tip_pickTile", false ); + mMainPanel->add( mToolSet ); + + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "roomName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mSetNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mSetNameField ); + + mSetNameField->setFocus( true ); + //mSetNameField->lockFocus( true ); + + mSetNameField->setCursorPosition( strlen( defaultText ) ); + + mSetNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + G * buttonSize; + + double extra = gameHeight - gridEdge; + + + // center it vertically on room picker + double addY = mainRoomPicker->getAnchorY() + + mainRoomPicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addRoom" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + gameWidth / 2, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + mWallsButton = new SelectableButtonGL( + new Sprite( "walls.tga", true ), + 1, + sideButtonsX - 2, mMiniViewButton->getAnchorY() + 30, + 20, 20 ); + + mMainPanel->add( mWallsButton ); + + mWallsButton->setSelected( false ); + + mWallsButton->addActionListener( this ); + mWallsButton->setToolTip( "tip_walls" ); + + + + mHintsButton = new SelectableButtonGL( + new Sprite( "hints.tga", true ), + 1, + sideButtonsX - 2, mMiniViewButton->getAnchorY() + 60, + 20, 20 ); + + mMainPanel->add( mHintsButton ); + + mHintsButton->setSelected( false ); + + mHintsButton->addActionListener( this ); + mHintsButton->setToolTip( "tip_hints" ); + + + + double undoButtonY = gameWidth - ( 48 + offset + (G - 1) * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + mClearButton = new ClearButtonGL( sideButtonsX, addY - 20, 16, 16 ); + mMainPanel->add( mClearButton ); + mClearButton->addActionListener( this ); + + + + + setRoomToEdit( mainRoomPicker->getSelectedResource() ); + + + mPenDown = false; + + + GridOverlay *overlay = new GridOverlay( + 8, + gameWidth - ( 48 + offset + (G - 1) * buttonSize ), + G * buttonSize, G * buttonSize, + G ); + + mMainPanel->add( overlay ); + + + // do this later, after state display added + //postConstruction(); + } + + + +RoomEditor::~RoomEditor() { + mSidePanel->remove( mainTilePicker ); + mSidePanel->remove( mainRoomPicker ); + + if( mStateDisplay != NULL ) { + mMainPanel->remove( mStateDisplay ); + } + } + + + +void RoomEditor::setGameStateDisplay( GameStateDisplay *inDisplay ) { + mStateDisplay = inDisplay; + + mMainPanel->add( mStateDisplay ); + + // now ready to add top-layer GUI components + postConstruction(); + } + + + +void RoomEditor::takeOverGameStateDisplay() { + char showHints = mHintsButton->getSelected(); + + if( mStateDisplay != NULL ) { + + mStateDisplay->setEnabled( showHints ); + } + } + + + +void RoomEditor::setRoomToEdit( Room inRoom ) { + + // clear old working usages + removeUsages( mCurrentWorkingStateID ); + + mRoomToEdit = inRoom; + + for( int y=0; ysetSprite( t.getSprite() ); + char *tileName = t.getName(); + mButtonGrid[y][x]->setToolTip( tileName, false ); + delete [] tileName; + + if( mWallsButton->getSelected() ) { + mButtonGrid[y][x]->setHighlight( mRoomToEdit.getWall( x, y ) ); + } + + // add working usages + addUsage( mCurrentWorkingStateID, tileID ); + } + } + mainTilePicker->recheckDeletable(); + + refreshMiniView(); + + char *name = mRoomToEdit.getRoomName(); + + mSetNameField->setText( name ); + mSetNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + } + + + +void RoomEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mRoomToEdit.getSprite( false, false ) ); + } + + + +char RoomEditor::recursiveFill( int inX, int inY, + uniqueID inOldTile, uniqueID inNewTile, + drawTool inTool ) { + + if( equal( mRoomToEdit.getTile( inX, inY ), inOldTile ) + && + !equal( mRoomToEdit.getTile( inX, inY ), inNewTile ) ) { + + Tile t( inNewTile ); + + + mButtonGrid[inY][inX]->setSprite( t.getSprite() ); + + char *tileName = t.getName(); + mButtonGrid[inY][inX]->setToolTip( tileName, false ); + delete [] tileName; + + mRoomToEdit.editRoomTile( inX, inY, inNewTile ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldTile, inNewTile, inTool ); + } + if( inX < G - 1 ) { + recursiveFill( inX + 1, inY, inOldTile, inNewTile, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldTile, inNewTile, inTool ); + } + if( inY < G - 1 ) { + recursiveFill( inX, inY + 1, inOldTile, inNewTile, inTool ); + } + } + + return true; + } + + return false; + } + + + + +char RoomEditor::recursiveFill( int inX, int inY, + char inOldWall, char inNewWall, + drawTool inTool ) { + + if( mRoomToEdit.getWall( inX, inY ) == inOldWall + && + mRoomToEdit.getWall( inX, inY ) != inNewWall ) { + + mButtonGrid[inY][inX]->setHighlight( inNewWall ); + mRoomToEdit.editRoomWall( inX, inY, inNewWall ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldWall, inNewWall, inTool ); + } + if( inX < G - 1 ) { + recursiveFill( inX + 1, inY, inOldWall, inNewWall, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldWall, inNewWall, inTool ); + } + if( inY < G - 1 ) { + recursiveFill( inX, inY + 1, inOldWall, inNewWall, inTool ); + } + } + + return true; + } + + return false; + } + + + + +void RoomEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainTilePicker ) { + // new tile picked + } + else if( inTarget == mainRoomPicker ) { + // ignore if caused by our own Add action + if( ! mAddAction && + ! mainRoomPicker->wasLastActionFromPress() ) { + + // will change room + + mUndoStack.push_back( mRoomToEdit ); + mUndoButton->setEnabled( true ); + + setRoomToEdit( mainRoomPicker->getSelectedResource() ); + + mainTilePicker->recheckDeletable(); + + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mSetNameField ) { + mUndoStack.push_back( mRoomToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mRoomToEdit.editRoomName( mSetNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addRoom(); + } + else if( inTarget == mEditTileButton ) { + showTileEditor(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Room last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mRoomToEdit ); + mRedoButton->setEnabled( true ); + + setRoomToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Room next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mRoomToEdit ); + mUndoButton->setEnabled( true ); + + setRoomToEdit( next ); + } + else if( inTarget == mWallsButton ) { + // toggle + mWallsButton->setSelected( ! mWallsButton->getSelected() ); + + char showWalls = mWallsButton->getSelected(); + + for( int y=0; ysetHighlight( + mRoomToEdit.getWall( x, y ) && showWalls ); + } + } + + + } + else if( inTarget == mHintsButton ) { + // toggle + mHintsButton->setSelected( ! mHintsButton->getSelected() ); + + char showHints = mHintsButton->getSelected(); + + if( mStateDisplay != NULL ) { + + mStateDisplay->setEnabled( showHints ); + } + } + else if( inTarget == mClearButton ) { + mUndoStack.push_back( mRoomToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + + // stick default everywhere + Tile t = Tile::getDefaultResource(); + + uniqueID id = t.getUniqueID(); + + + for( int y=0; ysetHighlight( false ); + + mButtonGrid[y][x]->setSprite( t.getSprite() ); + + char *tileName = t.getName(); + mButtonGrid[y][x]->setToolTip( tileName, false ); + delete [] tileName; + + mRoomToEdit.editRoomTile( x, y, id ); + } + } + refreshMiniView(); + } + else { + // check grid + + char found = false; + + for( int y=0; ygetSelected() ) { + case pen: { + if( mWallsButton->getSelected() ) { + // edit walls with pen + + if( !mPenDown ) { + // set ink until released + mWallInk = ! mRoomToEdit.getWall( x, y ); + + mPenDown = true; + changed = true; + } + + // use ink already set + mRoomToEdit.editRoomWall( x, y, mWallInk ); + + mButtonGrid[y][x]->setHighlight( + mWallInk ); + } + else { + // place tiles + Tile t = + mainTilePicker->getSelectedResource(); + + uniqueID id = t.getUniqueID(); + + + if( ! equal( id, + mRoomToEdit.getTile( x, y ) ) ) { + mButtonGrid[y][x]->setSprite( + t.getSprite() ); + + char *tileName = t.getName(); + mButtonGrid[y][x]->setToolTip( tileName, + false ); + delete [] tileName; + + + mRoomToEdit.editRoomTile( x, y, id ); + refreshMiniView(); + + // don't count single pen dots as undoable + // until pen is released + // save undo state only on on initial + // presses + if( !mPenDown ) { + mPenDown = true; + changed = true; + } + } + } + + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + release = true; + } + } + break; + case horLine: + case verLine: + case fill: + if( ! mWallsButton->getSelected() ) { + changed = recursiveFill( + x, y, + mRoomToEdit.getTile( x, y ), + mainTilePicker->getSelectedResourceID(), + mToolSet->getSelected() ); + refreshMiniView(); + } + else { + + if( !mPenDown ) { + mWallInk = ! mRoomToEdit.getWall( x, y ); + } + + changed = recursiveFill( + x, y, + mRoomToEdit.getWall( x, y ), + mWallInk, + mToolSet->getSelected() ); + + refreshMiniView(); + + + } + if( !mPenDown ) { + mPenDown = true; + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + release = true; + } + break; + case pickColor: { + Tile t( mRoomToEdit.getTile( x, y ) ); + + mainTilePicker->setSelectedResource( t ); + } + break; + case stamp: + AppLog::error( + "Error: unsupported stamp tool used in" + "RoomEditor\n" ); + break; + } + + + + if( changed ) { + + // initial press, or step of fill + // we're certainly using the selected tile + addUsage( mCurrentWorkingStateID, + mainTilePicker->getSelectedResourceID() ); + mainTilePicker->recheckDeletable(); + + + mUndoStack.push_back( oldRoomState ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + if( release ) { + // will this be too slow? How else to track all + // usages in a consistent way? + + // only doing this on release, so maybe okay? + + removeUsages( mCurrentWorkingStateID ); + + for( int ty=0; tyrecheckDeletable(); + } + + + + + } + } + } + } + + + } + + + + +void RoomEditor::editorClosing() { + mStateDisplay->setHintMode( false ); + mStateDisplay->setEnabled( true ); + + addRoom(); + } + + + +void RoomEditor::addRoom() { + mAddAction = true; + mRoomToEdit.finishEdit(); + mainRoomPicker->setSelectedResource( mRoomToEdit, true ); + + mainTilePicker->recheckDeletable(); + + mAddAction = false; + } + + diff --git a/gameSource/RoomEditor.h b/gameSource/RoomEditor.h new file mode 100644 index 0000000..fa44753 --- /dev/null +++ b/gameSource/RoomEditor.h @@ -0,0 +1,114 @@ +#ifndef ROOM_EDITOR_INCLUDED +#define ROOM_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Room.h" +#include "GameStateDisplay.h" +#include "DrawToolSet.h" +#include "SizeLimitedVector.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class RoomEditor : public Editor { + + public: + + RoomEditor( ScreenGL *inScreen ); + + ~RoomEditor(); + + + // a bit messy, but this allows GameStateEditor and RoomEditor + // to share the same state display (so room editor can show + // object hints) + // + // Room Editor construction not complete until this has been called. + // + // We don't destroy this + void setGameStateDisplay( GameStateDisplay *inDisplay ); + + // called by GameStateEditor before it opens the RoomEditor + // tells us that the shared GameStateDisplay is now ours + void takeOverGameStateDisplay(); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + + protected: + + GameStateDisplay *mStateDisplay; + + + + // triggered by add button or close + void addRoom(); + + + AddButtonGL *mAddButton; + char mAddAction; + + void setRoomToEdit( Room inRoom ); + + void refreshMiniView(); + + + SpriteCellButtonGL *mButtonGrid[G][G]; + + + SpriteButtonGL *mMiniViewButton; + + Room mRoomToEdit; + + + EditButtonGL *mEditTileButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + ClearButtonGL *mClearButton; + + + DrawToolSet *mToolSet; + + SelectableButtonGL *mWallsButton; + SelectableButtonGL *mHintsButton; + + + // returns true if anything changed + char recursiveFill( int inX, int inY, + uniqueID inOldTile, uniqueID inNewTile, + drawTool inTool ); + + + // version for wall toggles + char recursiveFill( int inX, int inY, + char inOldWall, char inNewWall, + drawTool inTool ); + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mSetNameField; + + + char mPenDown; + char mWallInk; + + + // for usage tracking of objects in current state + uniqueID mCurrentWorkingStateID; + + + }; + +#endif diff --git a/gameSource/RoomPicker.cpp b/gameSource/RoomPicker.cpp new file mode 100644 index 0000000..c654773 --- /dev/null +++ b/gameSource/RoomPicker.cpp @@ -0,0 +1,17 @@ +#include "RoomPicker.h" + +// all share one stack +SimpleVector globalRoomStack; + + +RoomPicker::RoomPicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalRoomStack ) { + } + + + + +RoomPicker::~RoomPicker() { + + } diff --git a/gameSource/RoomPicker.h b/gameSource/RoomPicker.h new file mode 100644 index 0000000..b7d4fe6 --- /dev/null +++ b/gameSource/RoomPicker.h @@ -0,0 +1,40 @@ +#ifndef ROOM_PICKER_INCLUDED +#define ROOM_PICKER_INCLUDED + +#include "Room.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class RoomPicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + RoomPicker( double inAnchorX, double inAnchorY ); + + + + virtual ~RoomPicker(); + + }; + + + +#endif + + + diff --git a/gameSource/Scale.cpp b/gameSource/Scale.cpp new file mode 100644 index 0000000..50147bc --- /dev/null +++ b/gameSource/Scale.cpp @@ -0,0 +1,356 @@ +#include "Scale.h" +#include "resourceManager.h" + +#include "imageCache.h" + +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + + +Color toneLowColor( 0, 85.0/255, 128.0/255 ); +Color toneHighColor( 0, 170.0/255, 1.0 ); + + + + +// static inits +Scale *Scale::sBlankScale = NULL; + +void Scale::staticInit() { + sBlankScale = new Scale(); + // ensure that blank timbre is saved at least once + sBlankScale->finishEdit(); + } + +void Scale::staticFree() { + delete sBlankScale; + } + + + + +void Scale::setupDefault() { + memset( (void *)mHalftonesOn, 0, HT ); + + // default to minor pentatonic + mHalftonesOn[0] = 1; + mHalftonesOn[3] = 1; + mHalftonesOn[5] = 1; + mHalftonesOn[7] = 1; + mHalftonesOn[10] = 1; + + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +Scale::Scale() { + setupDefault(); + } + + + +char Scale::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mDataLength ) { + + memcpy( (void *)mHalftonesOn, inData, HT ); + + inLength -= HT; + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, &inData[mDataLength], inLength ); + return true; + } + } + return false; + } + + + +Scale::Scale( unsigned char *inData, int inLength ) + : mID( defaultID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + + +Scale::Scale( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "scale", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + + if( ! initFromData( data, length ) ) { + // failure + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // scale failed to load + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + } + + + +void Scale::editScale( int inHalftone, char inToneOn ) { + mHalftonesOn[inHalftone] = (unsigned char)inToneOn; + } + + + +char Scale::getToneOn( int inHalftone ) { + return mHalftonesOn[inHalftone]; + } + + + +int Scale::getNumOn() { + int sum = 0; + for( int y=0; y 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Scale name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + +#include "minorGems/util/stringUtils.h" + + +char *Scale::getScaleName() { + return stringDuplicate( mName ); + } + + + +#include "minorGems/util/SimpleVector.h" + + +unsigned char *Scale::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( mHalftonesOn, HT ); + + dataAccum.push_back( (unsigned char *)mName, + strlen( mName ) + 1 ); + + + *outLength = dataAccum.size(); + + return dataAccum.getElementArray(); + } + + + + +void Scale::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( ! equal( oldID, mID ) || + ! resourceExists( "scale", mID ) ) { + + // change + + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + saveResourceData( "scale", mID, + mName, + data, dataSize ); + + delete [] data; + } + } + + + +void Scale::saveToPack() { + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + addToPack( "scale", mID, + mName, + data, dataSize ); + + delete [] data; + } + + + + +uniqueID Scale::getUniqueID() { + return mID; + } + + + +const char *Scale::getResourceType() { + return "scale"; + } + +Scale Scale::getDefaultResource() { + return *( Scale::sBlankScale ); + } + + + + +void Scale::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, mHalftonesOn, HT ); + + p = addToUniqueID( p, (unsigned char*)mName, strlen( mName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *Scale::getImage() { + Image *image = new Image( P, P, 3, true ); + + double *channels[3]; + + int i; + + for( i=0; i<3; i++ ) { + channels[i] = image->getChannel( i ); + } + + Color *drawColorA = &toneLowColor; + Color *drawColorB = &toneHighColor; + + for( int y=0; y +void SizeLimitedVector::deleteElementOfType( + Scale inElement ) { + // no delete necessary + } + + + +ScaleEditor::ScaleEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_scale" ); + + mSidePanel->add( mainScalePicker ); + + mainScalePicker->addActionListener( this ); + + + + double offset = P; + + double buttonSize = (gameHeight - 2 * offset - 8) / P; + + + for( int y=0; ysetColor( c ); + + + mMainPanel->add( mScaleButtons[y] ); + + mScaleButtons[y]->addActionListener( this ); + } + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "scaleName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + buttonSize * P; + + double extra = gameHeight - gridEdge; + + + // center it vertically on scale picker + double addY = mainScalePicker->getAnchorY() + + mainScalePicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addScale" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + double undoButtonY = gameWidth - ( 48 + P * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + mPartToWatch = 0; + + setScaleToEdit( mainScalePicker->getSelectedResource() ); + + postConstruction(); + } + + + +ScaleEditor::~ScaleEditor() { + mSidePanel->remove( mainScalePicker ); + } + + +extern Color harmonicLowColor; +extern Color harmonicHighColor; + +#include "musicPlayer.h" + + + +void ScaleEditor::setPartToWatch( int inPartNumber ) { + mPartToWatch = inPartNumber; + + int toneNumber = 0; + int numTones = mScaleToEdit.getNumOn(); + + for( int y=0; ysetState( toneOn ); + if( toneOn ) { + mScaleButtons[y]->setMusicInfo( toneNumber, numTones, + mPartToWatch ); + + toneNumber++; + } + else { + mScaleButtons[y]->setMusicInfo( -1, numTones, mPartToWatch ); + } + } + } + + + + +void ScaleEditor::setScaleToEdit( Scale inScale ) { + mScaleToEdit = inScale; + + // redo button note monitors + setPartToWatch( mPartToWatch ); + + + + refreshMiniView(); + + char *name = mScaleToEdit.getScaleName(); + + mNameField->setText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + + + mScaleToEdit.setInPlayer(); + + if( mainSongEditor != NULL ) { + mainSongEditor->scaleChanged(); + } + } + + + +void ScaleEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mScaleToEdit.getSprite( false, false ) ); + } + + +void ScaleEditor::saveUndoPoint() { + mUndoStack.push_back( mScaleToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + +void ScaleEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainScalePicker ) { + Scale scalePicked = mainScalePicker->getSelectedResource(); + + if( ! mAddAction && + ! mainScalePicker->wasLastActionFromPress() ) { + // will change scale + + mUndoStack.push_back( mScaleToEdit ); + mUndoButton->setEnabled( true ); + + setScaleToEdit( scalePicked ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mNameField ) { + mUndoStack.push_back( mScaleToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mScaleToEdit.editScaleName( mNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addScale(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Scale last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mScaleToEdit ); + mRedoButton->setEnabled( true ); + + setScaleToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Scale next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mScaleToEdit ); + mUndoButton->setEnabled( true ); + + setScaleToEdit( next ); + } + else { + // check scale buttons + char found = false; + + for( int y=0; ysetEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + found = true; + + int oldToneOn = + mScaleToEdit.getToneOn( y ); + + mScaleToEdit.editScale( y, !oldToneOn ); + + setScaleToEdit( mScaleToEdit ); + } + } + } + + + } + + + +void ScaleEditor::editorClosing() { + addScale(); + } + + + +void ScaleEditor::addScale() { + mAddAction = true; + mScaleToEdit.finishEdit(); + mainScalePicker->setSelectedResource( mScaleToEdit, true ); + mAddAction = false; + } + diff --git a/gameSource/ScaleEditor.h b/gameSource/ScaleEditor.h new file mode 100644 index 0000000..d0524fe --- /dev/null +++ b/gameSource/ScaleEditor.h @@ -0,0 +1,75 @@ +#ifndef SCALE_EDITOR_INCLUDED +#define SCALE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Scale.h" +#include "SizeLimitedVector.h" + + + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + + +class ScaleEditor : public Editor { + + public: + + ScaleEditor( ScreenGL *inScreen ); + + ~ScaleEditor(); + + // for realtime highlights + void setPartToWatch( int inPart ); + + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addScale(); + + + AddButtonGL *mAddButton; + char mAddAction; + + void setScaleToEdit( Scale inScale ); + + void refreshMiniView(); + + void saveUndoPoint(); + + + ScaleToggleButton *mScaleButtons[HT]; + + + SpriteButtonGL *mMiniViewButton; + + Scale mScaleToEdit; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mNameField; + + + int mPartToWatch; + + }; + +#endif diff --git a/gameSource/ScalePicker.cpp b/gameSource/ScalePicker.cpp new file mode 100644 index 0000000..d051b8f --- /dev/null +++ b/gameSource/ScalePicker.cpp @@ -0,0 +1,18 @@ +#include "ScalePicker.h" + +// all share one stack +SimpleVector globalScaleStack; + + +ScalePicker::ScalePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, + &globalScaleStack ) { + } + + + + +ScalePicker::~ScalePicker() { + + } diff --git a/gameSource/ScalePicker.h b/gameSource/ScalePicker.h new file mode 100644 index 0000000..dfeed13 --- /dev/null +++ b/gameSource/ScalePicker.h @@ -0,0 +1,40 @@ +#ifndef SCALE_PICKER_INCLUDED +#define SCALE_PICKER_INCLUDED + +#include "Scale.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class ScalePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + ScalePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~ScalePicker(); + + }; + + + +#endif + + + diff --git a/gameSource/Scene.cpp b/gameSource/Scene.cpp new file mode 100644 index 0000000..450a44a --- /dev/null +++ b/gameSource/Scene.cpp @@ -0,0 +1,876 @@ +#include "Scene.h" +#include "resourceManager.h" +#include "usageDatabase.h" +#include "Room.h" +#include "StateObject.h" +#include "SpriteResource.h" +#include "imageCache.h" +#include "packSaver.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + + +static unsigned char sceneVersionNumber = 2; + + +// static inits +Scene *Scene::sBlankScene = NULL; + +void Scene::staticInit() { + sBlankScene = new Scene(); + // save once + sBlankScene->finishEdit(); + } + +void Scene::staticFree() { + delete sBlankScene; + } + + + +void Scene::setupDefault() { + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + mRoom = Room::sBlankRoom->getUniqueID(); + + mRoomTrans = 255; + + mObjectZeroFrozen = false; + + // okay that there are ZERO objects in the default, + // because when loaded against a blank game state, that game state + // will contain the default object position for Obj-Zero anyway, + // and we simply won't change it (because we have zero objects!) + + // No need to duplicate default object position and other attributes here. + + + makeUniqueID(); + } + + + + +Scene::Scene() { + setupDefault(); + } + + + + +// room loaded from file +Scene::Scene( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "scene", mID, &length, + &fromNetwork ); + + + if( data != NULL ) { + readFromBytes( data, length ); + + delete [] data; + + if( fromNetwork ) { + // room might not be present locally + // force it to load + Room loadedRoom( mRoom ); + + + + // objects might not be present locally, either + + // more efficient to load them in a chunk instead of with separate + // messages + + int numObjects = mObjects.size(); + + + if( numObjects > 0 ) { + + uniqueID *ids = mObjects.getElementArray(); + + int *chunkLengths = new int[ numObjects ]; + char *fromNetworkFlags = new char[ numObjects ]; + + + unsigned char **chunks = loadResourceData( "object", + numObjects, + ids, + chunkLengths, + fromNetworkFlags ); + + + + for( int i=0; i 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Scene name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + +char *Scene::getSceneName() { + return stringDuplicate( mName ); + } + + + +// finishes the edit, generates a new unique ID, saves result +void Scene::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( !equal( oldID, mID ) || + ! resourceExists( "scene", mID ) ) { + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "scene", mID, mName, bytes, numBytes ); + delete [] bytes; + + + // track usages + addUsage( mID, mRoom ); + + int numObjects = mObjects.size(); + + for( int j=0; jgetUniqueID() ) ) { + + Tile t( tID ); + + + Image *tImage = t.getImage(); + + for( int py=0; pygetColor( py * P + px ); + + // darken based on trans + c.r *= roomTrans; + c.g *= roomTrans; + c.b *= roomTrans; + + fullImage.setColor( fullY * imageW + fullX, + c ); + + if( fullX < lowX ) { + lowX = fullX; + } + if( fullX > hiX ) { + hiX = fullX; + } + if( fullY < lowY ) { + lowY = fullY; + } + if( fullY > hiY ) { + hiY = fullY; + } + } + } + delete tImage; + } + + } + } + + + /* + // FIXME lay tile sprites in... actually, should be okay to lay in + // big 16x16 blocks of color (taken from room thumbnail sprite) + // because that is what would happen with area mapping reduction anyway + + Image *roomImage = room.getImage(); + + double *roomChannels[3] = + { roomImage->getChannel( 0 ), + roomImage->getChannel( 1 ), + roomImage->getChannel( 2 ) }; + + double *fullChannels[4] = + { fullImage.getChannel( 0 ), + fullImage.getChannel( 1 ), + fullImage.getChannel( 2 ), + fullImage.getChannel( 3 ) }; + + + double roomTrans = mRoomTrans / 255.0f; + + for( int y=0; y 0 || + roomChannels[1][gi] > 0 || + roomChannels[2][gi] > 0 ) { + + // track areas hit with color + if( x < lowX ) { + lowX = x; + } + if( x > hiX ) { + hiX = x; + } + if( y < lowY ) { + lowY = y; + } + if( y > hiY ) { + hiY = y; + } + + for( int c=0; c<3; c++ ) { + // darken based on trans + fullChannels[c][fullI] = + roomTrans * roomChannels[c][gi]; + } + fullChannels[3][fullI] = 1.0f; + } + } + } + + delete roomImage; + */ + + + int numObjects = mObjects.size(); + + for( int j=0; j= 0 + && + fullX < imageW && fullX >= 0 ) { + + int index = y * P + x; + + Color c = spriteImage->getColor( index ); + + float colorTrans = c.a * layerTrans; + + + if( colorTrans > 0 ) { + + int fullIndex = fullY * imageW + fullX; + Color oldColor = + fullImage.getColor( fullIndex ); + + Color *blend; + + if( oldColor.a > 0 ) { + + blend = Color::linearSum( + &c, &( oldColor ), + colorTrans ); + } + else { + blend = c.copy(); + blend->a = colorTrans; + } + // overwrite with blend of ink from higher + // layer + + fullImage.setColor( fullIndex, *blend ); + + delete blend; + + + if( fullX < lowX ) { + lowX = fullX; + } + if( fullX > hiX ) { + hiX = fullX; + } + if( fullY < lowY ) { + lowY = fullY; + } + if( fullY > hiY ) { + hiY = fullY; + } + } + } + + } + } + + + delete spriteImage; + } + } + + /* + PNGImageConverter png; + + File pngFile( NULL, "fullTest.png" ); + FileOutputStream out( &pngFile ); + png.formatImage( &fullImage, &out ); + */ + + int subW = hiX - lowX + 1; + int subH = hiY - lowY + 1; + + Image *subImage = fullImage.getSubImage( lowX, lowY, + subW, subH ); + + + + + //printf( "subimage = x,y=(%d,%d), w,h=(%d,%d)\n", + // lowX, lowY, subW, subH ); + + // build up sums here + Image *shrunkImage = new Image( P, P, 4, true ); + double *subChannels[4]; + double *shrunkChannels[4]; + int hitCount[P*P]; + memset( hitCount, 0, P*P*sizeof(int) ); + + for( int c=0; c<4; c++ ) { + subChannels[c] = subImage->getChannel( c ); + shrunkChannels[c] = shrunkImage->getChannel( c ); + } + + double scaleFactor; + + if( subW > subH ) { + scaleFactor = P / (double)subW; + } + else { + scaleFactor = P / (double)subH; + } + + if( scaleFactor > 1 ) { + // P big enough to contain whole scene + scaleFactor = 1; + } + + + // center in shrunk image + int xExtra = (int)( P - ( scaleFactor * subW ) ); + int yExtra = (int)( P - ( scaleFactor * subH ) ); + + int xOffset = xExtra / 2; + int yOffset = yExtra / 2; + + + + for( int y=0; y 0 ) { + + hitCount[shrunkIndex] ++; + + for( int c=0; c<3; c++ ) { + shrunkChannels[c][shrunkIndex] += + subChannels[c][subIndex]; + } + // anything hit by non-trans color is opaque + shrunkChannels[3][shrunkIndex] = 1; + } + } + } + + + + for( int c=0; c<3; c++ ) { + for( int i=0; i 0 ) { + shrunkChannels[c][i] /= hitCount[i]; + } + } + } + + // sharp transparency already computed above + // (skipped trans pixels in sums) + + + + delete subImage; + + cachedImage = shrunkImage; + + if( inCacheOK ) { + addCachedImage( mID, inUseTrans, cachedImage ); + } + } + + + + Sprite *sprite = new Sprite( cachedImage ); + + if( !inCacheOK ) { + // image not in cache... we must destroy it + delete cachedImage; + } + + return sprite; + } + + + +const char *Scene::getResourceType() { + return "scene"; + } + + + +Scene Scene::getDefaultResource() { + return *sBlankScene; + } + + + + + + +#include "minorGems/util/SimpleVector.h" + +unsigned char *Scene::makeBytes( int *outLength ) { + SimpleVector buffer; + + buffer.push_back( (unsigned char *)SID_MAGIC_CODE, + strlen( SID_MAGIC_CODE ) ); + + buffer.push_back( sceneVersionNumber ); + + + + buffer.push_back( mRoom.bytes, U ); + + buffer.push_back( mRoomTrans ); + + buffer.push_back( (unsigned char)mObjectZeroFrozen ); + + + + + unsigned char numObjects = (unsigned char)mObjects.size(); + + // one byte for num objects (max 255 objects) + buffer.push_back( numObjects ); + + for( int i=0; ibytes, U ); + + intPair *p = mObjectOffsets.getElement( i ); + + // full precision + buffer.push_back( getChars( *p ), 8 ); + + p = mSpeechOffsets.getElement( i ); + + buffer.push_back( getChars( *p ), 8 ); + + buffer.push_back( *( mSpeechFlipFlags.getElement( i ) ) ); + buffer.push_back( *( mSpeechBoxFlags.getElement( i ) ) ); + buffer.push_back( *( mLockedFlags.getElement( i ) ) ); + buffer.push_back( *( mObjectTrans.getElement( i ) ) ); + } + + buffer.push_back( (unsigned char *)mName, strlen( mName ) + 1 ); + + *outLength = buffer.size(); + return buffer.getElementArray(); + } + + + +void Scene::readFromBytes( unsigned char *inBytes, int inLength ) { + + unsigned char version = 1; + + // check for magic code before version number + const char *code = SID_MAGIC_CODE; + int codeLength = strlen( code ); + + if( inLength >= codeLength ) { + char codeFound = true; + + + for( int i=0; i= 2 ) { + + mSpeechBoxFlags.push_back( inBytes[0] ); + + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + + mLockedFlags.push_back( inBytes[0] ); + + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + } + else { + // fill in with dummy data + mSpeechBoxFlags.push_back( 0 ); + mLockedFlags.push_back( 0 ); + } + + + mObjectTrans.push_back( inBytes[0] ); + + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + } + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, inBytes, inLength ); + } + } + + + +void Scene::makeUniqueID() { + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + mID = ::makeUniqueID( bytes, numBytes ); + delete [] bytes; + } + + + +void Scene::print() { + char *idString = getHumanReadableString( mID ); + + printf( "Scene %s:\n", idString ); + + delete [] idString; + + + for( int i=0; ix, p->y ); + delete [] idString; + } + printf( "\n" ); + } diff --git a/gameSource/Scene.h b/gameSource/Scene.h new file mode 100644 index 0000000..31c4bbc --- /dev/null +++ b/gameSource/Scene.h @@ -0,0 +1,106 @@ +#ifndef SCENE_INCLUDED +#define SCENE_INCLUDED + + +#include "color.h" +#include "common.h" +#include "uniqueID.h" +#include "Sprite.h" + +#include "minorGems/util/SimpleVector.h" + + +class Scene { + public: + + // room filled with blank tiles and default name + Scene(); + + + // room loaded from file + Scene( uniqueID inID ); + + // name has at most 10 chars + void editSceneName( const char *inName ); + + char *getSceneName(); + + + + // finishes the edit, generates a new unique ID, saves result + void finishEdit( char inGenerateNewID=true ); + + // recursively saves to current resource pack + void saveToPack(); + + + // implements ResourceType functions as needed by ResourcePicker + uniqueID getUniqueID(); + Sprite *getSprite( char inUseTrans=false, char inCacheOK=true ); + static const char *getResourceType(); + static Scene getDefaultResource(); + + char *getName() { + return getSceneName(); + } + + + + + // blank object + static Scene *sBlankScene; + + static void staticInit(); + static void staticFree(); + + + + void print(); + + + // not part of state that is saved to disk + // used only by editor + int mSelectedLayer; + + + // these are public to avoid needed to write a bunch of set/get + // functions + uniqueID mRoom; + + unsigned char mRoomTrans; + + char mObjectZeroFrozen; + + SimpleVector mObjects; + SimpleVector mObjectOffsets; + SimpleVector mSpeechOffsets; + SimpleVector mSpeechFlipFlags; + SimpleVector mSpeechBoxFlags; + SimpleVector mLockedFlags; + SimpleVector mObjectTrans; + + + protected: + void setupDefault(); + + + // result destroyed by caller + unsigned char *makeBytes( int *outLength ); + + void readFromBytes( unsigned char *inBytes, int inLength ); + + + void makeUniqueID(); + + + + + char mName[11]; + + uniqueID mID; + + }; + + + +#endif diff --git a/gameSource/ScenePicker.cpp b/gameSource/ScenePicker.cpp new file mode 100644 index 0000000..2d4c6c7 --- /dev/null +++ b/gameSource/ScenePicker.cpp @@ -0,0 +1,17 @@ +#include "ScenePicker.h" + +// all share one stack +SimpleVector globalSceneStack; + + +ScenePicker::ScenePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalSceneStack ) { + } + + + + +ScenePicker::~ScenePicker() { + + } diff --git a/gameSource/ScenePicker.h b/gameSource/ScenePicker.h new file mode 100644 index 0000000..7eed0c2 --- /dev/null +++ b/gameSource/ScenePicker.h @@ -0,0 +1,40 @@ +#ifndef SCENE_PICKER_INCLUDED +#define SCENE_PICKER_INCLUDED + +#include "Scene.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class ScenePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + ScenePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~ScenePicker(); + + }; + + + +#endif + + + diff --git a/gameSource/SelectionManager.cpp b/gameSource/SelectionManager.cpp new file mode 100644 index 0000000..402b37c --- /dev/null +++ b/gameSource/SelectionManager.cpp @@ -0,0 +1,96 @@ +#include "SelectionManager.h" + + + + +char SelectionManager::sSelection[P][P]; + +rgbaColor SelectionManager::sColor[P][P]; +char SelectionManager::sTrans[P][P]; + +static rgbaColor black = { {0,0,0,255} }; + +void SelectionManager::clearSelection() { + for( int y=0; y maxX ) { + maxX = x; + } + if( y > maxY ) { + maxY = y; + } + } + } + } + + intPair result = { ( minX + maxX ) / 2, + ( minY + maxY ) / 2 }; + + + return result; + } + + diff --git a/gameSource/SelectionManager.h b/gameSource/SelectionManager.h new file mode 100644 index 0000000..724724c --- /dev/null +++ b/gameSource/SelectionManager.h @@ -0,0 +1,43 @@ +#include "common.h" +#include "color.h" + + +// maintains a global selection on a PxP grid +class SelectionManager { + public: + + static void clearSelection(); + + + static char isInSelection( int inX, int inY ); + + + static rgbaColor getColor( int inX, int inY ); + + static char getTrans( int inX, int inY ); + + + static void toggleSelection( int inX, int inY, char inSelected ); + + + static void setColor( int inX, int inY, rgbaColor inColor ); + static void setTrans( int inX, int inY, char inTrans ); + + + // if trans ignored, center computed based on colored pixels only + static intPair getSelectionCenter( char inIgnoreTrans = false ); + + + + protected: + + static char sSelection[P][P]; + + static rgbaColor sColor[P][P]; + + static char sTrans[P][P]; + + }; + + + diff --git a/gameSource/SizeLimitedVector.h b/gameSource/SizeLimitedVector.h new file mode 100644 index 0000000..10c3388 --- /dev/null +++ b/gameSource/SizeLimitedVector.h @@ -0,0 +1,103 @@ +#ifndef SIZE_LIMITED_VECTOR_INCLUDED +#define SIZE_LIMITED_VECTOR_INCLUDED + + +#include "minorGems/util/SimpleVector.h" + + +// vector that trims earlier elements if size passes double of maxSize + +template +class SizeLimitedVector : public SimpleVector { + + public: + + + // inDeleteWhenCulling true to perform a "delete" operation + // on culled elements before discarding them (should be set + // when vector contains pointers) + SizeLimitedVector( int inLimit, char inDeleteWhenCulling ); + + + + // override + void push_back( Type x ); + + + protected: + + int mLimit; + char mDeleteWhenCulling; + + + // must be implemented specially for each instantiation types + // used to destroy elements when culling excess in vector + // + // can do nothing if no element destruction necessary + // Only called if inDeleteWhenCulling is set in constructor + void deleteElementOfType( Type inElement ); + + + }; + + + + + +template +inline SizeLimitedVector::SizeLimitedVector( int inLimit, + char inDeleteWhenCulling ) + : mLimit( inLimit ), + mDeleteWhenCulling( inDeleteWhenCulling ) { + } + + + + +template +inline void SizeLimitedVector::push_back( Type x ) { + SimpleVector::push_back( x ); + + int oldNum = SimpleVector::size(); + + + if( oldNum >= mLimit * 2 ) { + printf( "Passed limit of %d with %d elements...\n", + mLimit, oldNum ); + + // cull back down to mLimit elements by removing excess + + int numToRemove = oldNum - mLimit; + + + if( mDeleteWhenCulling ) { + for( int i=0; i::elements[i]; + deleteElementOfType( SimpleVector::elements[i] ); + } + } + + + + for( int i=numToRemove; i::elements[i - numToRemove] = + SimpleVector::elements[i]; + } + + SimpleVector::numFilledElements -= numToRemove; + + + printf( "...After culling, %d left\n", SimpleVector::size() ); + } + + } + + + +#endif + + diff --git a/gameSource/Song.cpp b/gameSource/Song.cpp new file mode 100644 index 0000000..1ff1e1f --- /dev/null +++ b/gameSource/Song.cpp @@ -0,0 +1,686 @@ +#include "Song.h" +#include "Music.h" +#include "TimbreResource.h" +#include "Scale.h" +#include "resourceManager.h" +#include "usageDatabase.h" +#include "uniqueID.h" + +#include "imageCache.h" +#include "packSaver.h" + + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + + + + + +// static inits +Song *Song::sBlankSong = NULL; + +void Song::staticInit() { + sBlankSong = new Song(); + // save once + sBlankSong->finishEdit(); + } + +void Song::staticFree() { + delete sBlankSong; + } + + + +void Song::setupDefault() { + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + + uniqueID blankID = Music::sBlankMusic->getUniqueID(); + + int x; + int y; + for( y=0; ygetUniqueID(); + for( x=0; xgetUniqueID(); + + // normal, default + mSpeed = 1; + + makeUniqueID(); + } + + + + +Song::Song() { + setupDefault(); + } + + + + +// song loaded from file +Song::Song( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "song", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + // save this one + initFromData( data, length, fromNetwork, true ); + + + delete [] data; + data = NULL; + } + else { + setupDefault(); + } + } + + +Song::Song( uniqueID inID, unsigned char *inData, int inLength, + char inFromNetwork, char inSaveWholeSong ) + : mID( inID ) { + + initFromData( inData, inLength, inFromNetwork, inSaveWholeSong ); + } + + + + +void Song::initFromData( unsigned char *inData, int inLength, + char inFromNetwork, char inSaveWholeSong ) { + + readFromBytes( inData, inLength ); + + if( inFromNetwork ) { + + // phrases might not be present locally, either + + // more efficient to load them in a chunk instead of with separate + // messages + + + uniqueID ids[SI*S]; + + int i=0; + + for( int ty=0; ty 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Song name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + + +uniqueID Song::getPhrase( int inX, int inY ) { + return mPhrases[inY][inX]; + } + + +int Song::getRowLength( int inY ) { + return mRowLengths[inY]; + } + + +int Song::getRowLoudness( int inY ) { + return mRowLoudness[inY]; + } + +int Song::getRowStereo( int inY ) { + return mRowStereo[inY]; + } + + +uniqueID Song::getTimbre( int inY ) { + return mTimbres[inY]; + } + + +uniqueID Song::getScale() { + return mScale; + } + + +int Song::getSpeed() { + return mSpeed; + } + + + +char *Song::getSongName() { + return stringDuplicate( mName ); + } + + + +// finishes the edit, generates a new unique ID, saves result +void Song::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( !equal( oldID, mID ) || + ! resourceExists( "song", mID ) ) { + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "song", mID, mName, bytes, numBytes ); + delete [] bytes; + + + // track usages + addUsage( mID, mScale ); + + for( int y=0; ygetChannel( 0 ), + fullImage->getChannel( 1 ), + fullImage->getChannel( 2 ) }; + + + + for( int py=0; pygetChannel( 0 ), + tImage->getChannel( 1 ), + tImage->getChannel( 2 ), + tImage->getChannel( 3 ) }; + + // compute average of all non-trans phrase pixels + + for( int c=0; c<3; c++ ) { + // sum first + int numNonTrans = 0; + + for( int i=0; i 0 ) { + fullC[c][fullP] += tC[c][i]; + numNonTrans ++; + } + } + if( numNonTrans > 0 ) { + fullC[c][fullP] /= numNonTrans; + fullC[c][fullP] *= (timbreColor)[c]; + } + else { + fullC[c][fullP] = 0; + } + } + delete tImage; + } + } + + return fullImage; + } + + + +Sprite *Song::getSprite( char inUseTrans, char inCacheOK ) { + // ignore inUseTrans + + Image *cachedImage = NULL; + + if( inCacheOK ) { + cachedImage = getCachedImage( mID, false ); + } + + if( cachedImage == NULL ) { + + + + cachedImage = getImage(); + + if( inCacheOK ) { + addCachedImage( mID, false, cachedImage ); + } + } + + + Sprite *sprite = new Sprite( cachedImage ); + if( !inCacheOK ) { + // image not in cache... we must destroy it + delete cachedImage; + } + return sprite; + } + + + +const char *Song::getResourceType() { + return "song"; + } + + + +Song Song::getDefaultResource() { + return *sBlankSong; + } + + + + + + +#include "minorGems/util/SimpleVector.h" + +unsigned char *Song::makeBytes( int *outLength ) { + SimpleVector buffer; + + int y, x; + + for( y=0; y= SI ) { + + memcpy( (void *)mRowLengths, inBytes, SI ); + + inBytes = &( inBytes[ SI ] ); + inLength -= SI; + } + + success = readUniqueIDs( (uniqueID *)mTimbres, SI, + inBytes, inLength, + &numBytesUsed ); + + if( success ) { + inBytes = &( inBytes[ numBytesUsed ] ); + inLength -= numBytesUsed; + } + + if( inLength >= SI ) { + + memcpy( (void *)mRowLoudness, inBytes, SI ); + + inBytes = &( inBytes[ SI ] ); + inLength -= SI; + } + + if( inLength >= SI ) { + + memcpy( (void *)mRowStereo, inBytes, SI ); + + inBytes = &( inBytes[ SI ] ); + inLength -= SI; + } + + mScale = readUniqueID( inBytes, inLength, + &numBytesUsed ); + if( numBytesUsed == U ) { + inBytes = &( inBytes[ numBytesUsed ] ); + inLength -= numBytesUsed; + } + + if( inLength > 0 ) { + mSpeed = inBytes[0]; + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + } + + + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, inBytes, inLength ); + } + } + + + +void Song::makeUniqueID() { + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + mID = ::makeUniqueID( bytes, numBytes ); + delete [] bytes; + } + + + + + +#include "musicPlayer.h" + +extern char noteToggles[PARTS][S][N][N]; + +extern int partLengths[PARTS]; +extern double partLoudness[PARTS]; +extern double partStereo[PARTS]; + + +void Song::setInPlayer( Song inOldSong, Song inNewSong, char inForceUpdate ) { + + // timbre updates are slow... only do the ones we need + uniqueID oldTimbres[SI]; + for( int y=0; y x ) { + for( int py=0; py + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; + +extern int partLengths[PARTS]; +extern double partLoudness[PARTS]; +extern double partStereo[PARTS]; + + +extern MusicPicker *mainMusicPicker; +extern SongPicker *mainSongPicker; +extern TimbrePicker *mainTimbrePicker; +extern TimbreEditor *mainTimbreEditor; + +extern ScalePicker *mainScalePicker; +extern ScaleEditor *mainScaleEditor; + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + + + + +template <> +void SizeLimitedVector::deleteElementOfType( + Song inElement ) { + // no delete necessary + } + + + + +SongEditor::SongEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ), + // gen \fake IDs + mCurrentWorkingPhrasesID( makeUniqueID( + (unsigned char*)"currSongPhrases", + strlen( "currSongPhrases" ) ) ), + mCurrentWorkingTimbresID( makeUniqueID( + (unsigned char*)"currSongTimbres", + strlen( "currSongTimbres" ) ) ) { + + AppLog::info( "Constructing song editor\n" ); + + mCloseButton->setToolTip( "tip_closeEdit_song" ); + + /* + LabelGL *titleLabel = new LabelGL( 0.75, 0.5, 0.25, + 0.1, "Song Editor", largeText ); + + + + mSidePanel->add( titleLabel ); + */ + + mSidePanel->add( mainMusicPicker ); + + mainMusicPicker->addActionListener( this ); + + + mSidePanel->add( mainSongPicker ); + + mainSongPicker->addActionListener( this ); + + + mainTimbrePicker->addActionListener( this ); + + mainScalePicker->addActionListener( this ); + + + mEditMusicButton = + new EditButtonGL( + mainMusicPicker->getAnchorX() - 1, + mainMusicPicker->getAnchorY() + mainMusicPicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mEditMusicButton ); + + mEditMusicButton->addActionListener( this ); + + mEditMusicButton->setToolTip( "tip_edit_phrase" ); + + /* + // 1-pixel wide white border + // inner panel to provide a 1-pixel wide black gap + + BorderPanel *workingColorBorderPanel = + new BorderPanel( 256, 206, 48, 14, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + 1 ); + + + // smaller to leave black gap + GUIPanelGL *workingColorPanel = new GUIPanelGL( 258, 208, 44, 10, + mWorkingColor ); + + mSidePanel->add( workingColorBorderPanel ); + workingColorBorderPanel->add( workingColorPanel ); + + + mAddButton = new AddButtonGL( 272, 184, 16, 16 ); + mSidePanel->add( mAddButton ); + mAddButton->addActionListener( this ); + + */ + + + mEmptyPhraseSprite = new Sprite( "emptyPhrase.tga", true ); + + + double offset = P; + + double buttonSize = P; + + double timbreButtonSize = P + 4; + + // for volume sliders + Color *thumbColor = new Color( .5, .5, .5, .5 ); + Color *borderColor = new Color( .35, .35, .35, .35 ); + + for( int y=0; ysetBlankSprite( mEmptyPhraseSprite ); + + mButtonGrid[y][x]->setMusicInfo( y, x ); + + mMainPanel->add( mButtonGrid[y][x] ); + + mButtonGrid[y][x]->addActionListener( this ); + } + mTimbreButtons[y] = new SpriteButtonGL( + NULL, 1, + mButtonGrid[y][0]->getAnchorX() - timbreButtonSize - 2, + mButtonGrid[y][0]->getAnchorY() - 2, + timbreButtonSize, + timbreButtonSize ); + + mMainPanel->add( mTimbreButtons[y] ); + + + mEditTimbreButtons[y] = + new EditButtonGL( + mTimbreButtons[y]->getAnchorX() - 8, + mTimbreButtons[y]->getAnchorY() + + mTimbreButtons[y]->getHeight() - 8, + 8, + 8 ); + + mMainPanel->add( mEditTimbreButtons[y] ); + + mEditTimbreButtons[y]->addActionListener( this ); + + mEditTimbreButtons[y]->setToolTip( "tip_edit_timbre" ); + + MusicCellButtonGL *rowLastButton = + mButtonGrid[y][S-1]; + + + mLoudnessSliders[y] = + new ToolTipSliderGL( rowLastButton->getAnchorX() + 18, + rowLastButton->getAnchorY() + 8, + 14, 10, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mLoudnessSliders[y] ); + mLoudnessSliders[y]->setThumbPosition( 1.0 ); + mLoudnessSliders[y]->addActionListener( this ); + mLoudnessSliders[y]->setToolTip( "tip_trackLoudness" ); + + + mStereoSliders[y] = + new ToolTipSliderGL( rowLastButton->getAnchorX() + 18, + rowLastButton->getAnchorY() - 2, + 14, 10, + NULL, 0, + new Color( 0, 1, 0, 1 ), + new Color( 1, 0, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mStereoSliders[y] ); + mStereoSliders[y]->setThumbPosition( 1.0 ); + mStereoSliders[y]->addActionListener( this ); + mStereoSliders[y]->setToolTip( "tip_trackStereo" ); + } + + delete thumbColor; + delete borderColor; + + + + + // no stamp tool here + mToolSet = new DrawToolSet( 2, 42, "tip_pickPhrase", false ); + mMainPanel->add( mToolSet ); + + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "songName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mSetNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mSetNameField ); + + mSetNameField->setFocus( true ); + //mSetNameField->lockFocus( true ); + + mSetNameField->setCursorPosition( strlen( defaultText ) ); + + mSetNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + G * buttonSize; + + double extra = gameHeight - gridEdge; + + + // center it vertically on song picker + double addY = mainSongPicker->getAnchorY() + + mainSongPicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addSong" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + gameWidth / 2 + 2, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + /* + mWallsButton = new SelectableButtonGL( + new Sprite( "walls.tga", true ), + 1, + sideButtonsX - 2, mMiniViewButton->getAnchorY() + 30, + 20, 20 ); + + mMainPanel->add( mWallsButton ); + + mWallsButton->setSelected( false ); + + mWallsButton->addActionListener( this ); + mWallsButton->setToolTip( "tip_walls" ); + */ + + + mEraseButton = new SelectableButtonGL( + new Sprite( "erase.tga", true ), + 1, + sideButtonsX - 2, mMiniViewButton->getAnchorY() + 30, + //sideButtonsX - 2, undoButtonY + 20 + 19, + 20, 20 ); + + mMainPanel->add( mEraseButton ); + + mEraseButton->setSelected( false ); + + mEraseButton->addActionListener( this ); + + mEraseButton->setToolTip( "tip_erase" ); + + + + + double undoButtonY = gameWidth - ( 48 + offset + (G - 1) * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + mClearButton = new ClearButtonGL( sideButtonsX, addY - 20, 16, 16 ); + mMainPanel->add( mClearButton ); + mClearButton->addActionListener( this ); + + + + // scale button in side panel + double scaleButtonY = + mainSongPicker->getAnchorY() + mainSongPicker->getHeight() + + (int)( mainMusicPicker->getAnchorY() - + ( mainSongPicker->getAnchorY() + mainSongPicker->getHeight() ) + - 16 ) / 2; + + mEditScaleButton = new SpriteButtonGL( + new Sprite( "editScale.tga", true ), + 1, + mainSongPicker->getAnchorX() - 1, scaleButtonY, 16, 16 ); + + mSidePanel->add( mEditScaleButton ); + + mEditScaleButton->addActionListener( this ); + + mEditScaleButton->setToolTip( "tip_edit_scale" ); + + + + + mSpeedButtons[0] = new SelectableButtonGL( + new Sprite( "slow.tga", true ), + 1, + 3, gameWidth / 2 - 18, 20, 20 ); + mSpeedButtons[0]->setToolTip( "tip_slowSpeed" ); + + + mSpeedButtons[1] = new SelectableButtonGL( + new Sprite( "normalSpeed.tga", true ), + 1, + 3, gameWidth / 2 - 18 + 20, 20, 20 ); + mSpeedButtons[1]->setToolTip( "tip_normalSpeed" ); + + + mSpeedButtons[2] = new SelectableButtonGL( + new Sprite( "fast.tga", true ), + 1, + 3, gameWidth / 2 - 18 + 40, 20, 20 ); + mSpeedButtons[2]->setToolTip( "tip_fastSpeed" ); + + + + for( int i=0; i<3; i++ ) { + mMainPanel->add( mSpeedButtons[i] ); + mSpeedButtons[i]->addActionListener( this ); + } + + + + + mAddToPackButton = new ToggleSpriteButtonGL( + new Sprite( "pack.tga", true ), + new Sprite( "packAlreadyIn.tga", true ), + 1, + mainSongPicker->getAnchorX() + mainSongPicker->getWidth() - 22, + mainSongPicker->getAnchorY() + + mainSongPicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mAddToPackButton ); + + mAddToPackButton->addActionListener( this ); + + mAddToPackButton->setToolTip( "tip_addSongToPack" ); + mAddToPackButton->setSecondToolTip( "tip_songAlreadyInPack" ); + + + mSavePackButton = new SpriteButtonGL( + new Sprite( "packSave.tga", true ), + 1, + mainSongPicker->getAnchorX() + mainSongPicker->getWidth() - 7, + mainSongPicker->getAnchorY() + + mainSongPicker->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mSavePackButton ); + + mSavePackButton->addActionListener( this ); + + mSavePackButton->setToolTip( "tip_savePack" ); + + + + + + + // make sure all timbres get set to default + mFirstSongSet = false; + setSongToEdit( mainSongPicker->getSelectedResource() ); + + + mPenDown = false; + mLastRowTouched = 0; + mIgnoreSliders = false; + + postConstruction(); + } + + + +SongEditor::~SongEditor() { + mSidePanel->remove( mainMusicPicker ); + mSidePanel->remove( mainSongPicker ); + + delete mEmptyPhraseSprite; + } + + +Song SongEditor::getLiveSong() { + return mSongToEdit; + } + + + +void SongEditor::setSongToEdit( Song inSong ) { + + // clear old working usages + removeUsages( mCurrentWorkingPhrasesID ); + removeUsages( mCurrentWorkingTimbresID ); + + + // force update of everything only the first time + char forceUpdate = false; + if( !mFirstSongSet ) { + forceUpdate = true; + mFirstSongSet = true; + } + + Song::setInPlayer( mSongToEdit, inSong, forceUpdate ); + + + + // timbre updates are slow... only do the ones we need + uniqueID oldTimbres[SI]; + for( int y=0; ysetSelectedResource( s, + true, false ); + + char *name = s.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( "tip_edit_scale" ), + name ); + mEditScaleButton->setToolTip( tip ); + delete [] tip; + delete [] name; + } + + + mSongToEdit = inSong; + + for( int y=0; y x ) { + mButtonGrid[y][x]->setSprite( m.getSprite() ); + mButtonGrid[y][x]->setColor( buttonColor.copy() ); + char *name = m.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( "tip_trackPhrase" ), + name ); + + mButtonGrid[y][x]->setToolTip( tip ); + + delete [] tip; + delete [] name; + + // add working usage + addUsage( mCurrentWorkingPhrasesID, phraseID ); + } + else { + mButtonGrid[y][x]->setSprite( NULL ); + mButtonGrid[y][x]->setToolTip( "tip_trackPhraseEmpty" ); + } + } + + mTimbreButtons[y]->setSprite( t.getSprite() ); + + char *name = t.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( "tip_trackTimbre" ), + name ); + + mTimbreButtons[y]->setToolTip( tip ); + + delete [] tip; + delete [] name; + + + double loudness = mSongToEdit.getRowLoudness( y ) / 255.0; + double stereo = mSongToEdit.getRowStereo( y ) / 255.0; + + mIgnoreSliders = true; + // only 28 pixels in slider... avoid round-off errors + mLoudnessSliders[y]->setThumbPosition( + (int)( loudness * 28 ) / 28.0 ); + mStereoSliders[y]->setThumbPosition( + (int)( stereo * 28 ) / 28.0 ); + mIgnoreSliders = false; + } + + // usages changed (due to current working usages) + mainMusicPicker->recheckDeletable(); + mainTimbrePicker->recheckDeletable(); + + + for( int i=0; i<3; i++ ) { + mSpeedButtons[i]->setSelected( mSongToEdit.getSpeed() == i ); + } + + refreshMiniView(); + + + + + char *name = mSongToEdit.getSongName(); + + mSetNameField->setText( name ); + mSetNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + } + + + +void SongEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mSongToEdit.getSprite( false, false ) ); + } + + + +void SongEditor::scaleChanged() { + for( int y=0; ysetSprite( m.getSprite() ); + mSongToEdit.editSongPhrase( inX, inY, inNewMusic ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldMusic, inNewMusic, inTool ); + } + if( inX < S - 1 ) { + recursiveFill( inX + 1, inY, inOldMusic, inNewMusic, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldMusic, inNewMusic, inTool ); + } + if( inY < SI - 1 ) { + recursiveFill( inX, inY + 1, inOldMusic, inNewMusic, inTool ); + } + } + + return true; + } + + return false; + } + + + + + +char SongEditor::recursiveErase( int inX, int inY, + uniqueID inOldID, + drawTool inTool ) { + + if( mButtonGrid[inY][inX]->getSprite() != NULL && + equal( inOldID, mSongToEdit.getPhrase( inX, inY ) ) ) { + + mButtonGrid[inY][inX]->setSprite( NULL ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveErase( inX - 1, inY, inOldID, inTool ); + } + if( inX < S - 1 ) { + recursiveErase( inX + 1, inY, inOldID, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveErase( inX, inY - 1, inOldID, inTool ); + } + if( inY < SI - 1 ) { + recursiveErase( inX, inY + 1, inOldID, inTool ); + } + } + + return true; + } + + return false; + } + + +void SongEditor::saveUndoPoint() { + mUndoStack.push_back( mSongToEdit ); + mUndoButton->setEnabled( true ); + + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + + +void SongEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + for( int y=0; ysetPartToWatch( y ); + + mainTimbreEditor->setPlayerTimbreToEdit( y ); + + mainTimbrePicker->setSelectedResource( + mSongToEdit.getTimbre( y ) ); + + // working usages for all EXCEPT last row touched + // this allows us to delete a timbre, which if we're editing + // it, is ALWAYS present in the working song (at least in + // the last row touched). We should be allowed to delete it + // in that case, but not if it is also used in other working + // rows. + removeUsages( mCurrentWorkingTimbresID ); + + for( int y=0; yrecheckDeletable(); + + + showTimbreEditor(); + return; + } + + if( !mIgnoreSliders && inTarget == mLoudnessSliders[y] ) { + if( mLoudnessSliders[y]->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + mLastRowTouched = y; + mainScaleEditor->setPartToWatch( y ); + + double loudness = mLoudnessSliders[y]->getThumbPosition(); + partLoudness[y] = loudness; + mSongToEdit.editRowLoudness( y, (int)( loudness * 255 ) ); + return; + } + + if( !mIgnoreSliders && inTarget == mStereoSliders[y] ) { + if( mStereoSliders[y]->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + mLastRowTouched = y; + mainScaleEditor->setPartToWatch( y ); + + double stereo = mStereoSliders[y]->getThumbPosition(); + partStereo[y] = stereo; + mSongToEdit.editRowStereo( y, (int)( stereo * 255 ) ); + return; + } + } + + for( int i=0; i<3; i++ ) { + if( inTarget == mSpeedButtons[i] ) { + + if( ! mSpeedButtons[i]->getSelected() ) { + + mSpeedButtons[i]->setSelected( true ); + + mSpeedButtons[ (i+1) % 3 ]->setSelected( false ); + mSpeedButtons[ (i+2) % 3 ]->setSelected( false ); + + + saveUndoPoint(); + + mSongToEdit.editSpeed( i ); + + // set in player + setSpeed( i ); + + for( int y=0; ywasLastActionFromPress() ) { + // new music phrase picked + // erase mode off + mEraseButton->setSelected( false ); + } + + } + else if( inTarget == mainTimbrePicker ) { + if( ! mainTimbrePicker->wasLastActionFromPress() ) { + saveUndoPoint(); + + mSongToEdit.editTimbre( + mLastRowTouched, + mainTimbrePicker->getSelectedResourceID() ); + + // working usages for all EXCEPT last row touched + // this allows us to delete a timbre, which if we're editing + // it, is ALWAYS present in the working song (at least in + // the last row touched). We should be allowed to delete it + // in that case, but not if it is also used in other working + // rows. + removeUsages( mCurrentWorkingTimbresID ); + + for( int y=0; yrecheckDeletable(); + + + // just update that one timbre view (since music player already + // updated in realtime by timbre editor) + TimbreResource t( mSongToEdit.getTimbre( mLastRowTouched ) ); + mTimbreButtons[mLastRowTouched]->setSprite( t.getSprite() ); + + char *name = t.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( "tip_trackTimbre" ), + name ); + mTimbreButtons[mLastRowTouched]->setToolTip( tip ); + delete [] tip; + delete [] name; + + + Color buttonColor = toColor( t.getTimbreColor() ); + + + int numActive = mSongToEdit.getRowLength( mLastRowTouched ); + + for( int i=0; isetColor( + buttonColor.copy() ); + } + + //setSongToEdit( mSongToEdit ); + } + } + else if( inTarget == mainScalePicker ) { + if( ! mainTimbrePicker->wasLastActionFromPress() ) { + //printf( "Scale picker action\n" ); + + saveUndoPoint(); + + uniqueID oldScale = mSongToEdit.getScale(); + uniqueID newScale = mainScalePicker->getSelectedResourceID(); + + if( !equal( oldScale, newScale ) ) { + mSongToEdit.editScale( newScale ); + + Scale s( newScale ); + + char *name = s.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( "tip_edit_scale" ), + name ); + mEditScaleButton->setToolTip( tip ); + delete [] tip; + delete [] name; + + scaleChanged(); + } + } + } + else if( inTarget == mainSongPicker ) { + // ignore if caused by our own Add action + if( ! mAddAction && + ! mainSongPicker->wasLastActionFromPress() ) { + + // will change song + + saveUndoPoint(); + + setSongToEdit( mainSongPicker->getSelectedResource() ); + + mainMusicPicker->recheckDeletable(); + mainScalePicker->recheckDeletable(); + mainTimbrePicker->recheckDeletable(); + } + + mAddToPackButton->setState( + alreadyInPack( mainSongPicker->getSelectedResourceID() ) ); + } + else if( inTarget == mSetNameField ) { + saveUndoPoint(); + + mSongToEdit.editSongName( mSetNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addSong(); + } + else if( inTarget == mEditMusicButton ) { + // enable music editor extra part + partLengths[SI] = 1; + + // turn on last timbre for music editor + TimbreResource t( mSongToEdit.getTimbre( mLastRowTouched ) ); + t.setInPlayer( SI, true, true ); + + showMusicEditor(); + } + else if( inTarget == mEditScaleButton ) { + showScaleEditor(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Song last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mSongToEdit ); + mRedoButton->setEnabled( true ); + + setSongToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Song next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mSongToEdit ); + mUndoButton->setEnabled( true ); + + setSongToEdit( next ); + } + /* + else if( inTarget == mWallsButton ) { + // toggle + mWallsButton->setSelected( ! mWallsButton->getSelected() ); + + char showWalls = mWallsButton->getSelected(); + + for( int y=0; ysetHighlight( + mSongToEdit.getWall( x, y ) && showWalls ); + + } + } + + + } + */ + else if( inTarget == mEraseButton ) { + // toggle + mEraseButton->setSelected( ! mEraseButton->getSelected() ); + } + else if( inTarget == mClearButton ) { + saveUndoPoint(); + + setSongToEdit( Song::getDefaultResource() ); + } + else if( inTarget == mAddToPackButton ) { + AppLog::info( "Adding song to the current resource pack" ); + mainSongPicker->getSelectedResource().saveToPack(); + + mAddToPackButton->setState( true ); + } + else if( inTarget == mSavePackButton ) { + AppLog::info( "Saving the current resource pack" ); + savePack(); + + mAddToPackButton->setState( false ); + } + else { + // check grid + + char found = false; + + for( int y=0; yisPressed() ) { + + + found = true; + mLastRowTouched = y; + mainScaleEditor->setPartToWatch( y ); + + Song oldSongState = mSongToEdit; + char changed = false; + + switch( mToolSet->getSelected() ) { + case pen: { + if( mEraseButton->getSelected() ) { + // erase with pen + + if( mButtonGrid[y][x]->getSprite() != NULL ) { + + mButtonGrid[y][x]->setSprite( NULL ); + changed = true; + } + } + else { + // place phrase + Music m = + mainMusicPicker->getSelectedResource(); + + uniqueID id = m.getUniqueID(); + + + if( mButtonGrid[y][x]->getSprite() == NULL + || + ! equal( + id, + mSongToEdit.getPhrase( x, y ) ) ) { + mButtonGrid[y][x]->setSprite( + m.getSprite() ); + mSongToEdit.editSongPhrase( x, y, id ); + refreshMiniView(); + + + changed = true; + } + } + } + break; + case horLine: + case verLine: + case fill: + if( ! mEraseButton->getSelected() ) { + changed = recursiveFill( + x, y, + mSongToEdit.getPhrase( x, y ), + mainMusicPicker->getSelectedResourceID(), + mToolSet->getSelected() ); + refreshMiniView(); + } + else { + + changed = recursiveErase( + x, y, + mSongToEdit.getPhrase( x, y ), + mToolSet->getSelected() ); + + refreshMiniView(); + } + break; + case pickColor: { + Music m( mSongToEdit.getPhrase( x, y ) ); + + mainMusicPicker->setSelectedResource( m ); + if( mSongToEdit.getRowLength( y ) <= x ) { + // picked an empty spot + // erase mode on. + mEraseButton->setSelected( true ); + } + else { + // picked a full spot + // erase mode off + mEraseButton->setSelected( false ); + } + } + break; + case stamp: + AppLog::error( + "Error: unsupported stamp tool used in" + "SongEditor\n" ); + break; + } + + + + if( changed ) { + // we saved old song state from before, in + // case of change. + mUndoStack.push_back( oldSongState ); + mUndoButton->setEnabled( true ); + + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + + + // now, after whatever operation we performed above + // "smash" all active cells over to the left + + // update usages in process + removeUsages( mCurrentWorkingPhrasesID ); + + uniqueID defaultPhraseID = + Music::sBlankMusic->getUniqueID(); + + for( int cy=0; cy activeCells; + + for( int cx=0; cxgetSprite() != NULL ) { + activeCells.push_back( + mSongToEdit.getPhrase( cx, cy ) ); + } + + // clear here, set again below + mButtonGrid[cy][cx]->setSprite( NULL ); + mSongToEdit.editSongPhrase( cx, cy, + defaultPhraseID ); + } + + int numActive = activeCells.size(); + mSongToEdit.editRowLength( cy, numActive ); + + TimbreResource timbre = + TimbreResource( mSongToEdit.getTimbre( cy ) ); + Color buttonColor = + toColor( timbre.getTimbreColor() ); + + + + // propagate to music player + partLengths[cy] = numActive; + + for( int i=0; isetSprite( m.getSprite() ); + mButtonGrid[cy][i]->setColor( + buttonColor.copy() ); + + char *name = m.getName(); + char *tip = autoSprintf( + "%s (%s)", + TranslationManager::translate( + "tip_trackPhrase" ), + name ); + + mButtonGrid[cy][i]->setToolTip( tip ); + + delete [] tip; + delete [] name; + + + for( int py=0; pysetToolTip( + "tip_trackPhraseEmpty" ); + + for( int py=0; pyrecheckDeletable(); + + + } + } + } + } + + + } + + + + +void SongEditor::editorClosing() { + addSong(); + } + + + +void SongEditor::addSong() { + mAddAction = true; + mSongToEdit.finishEdit(); + mainSongPicker->setSelectedResource( mSongToEdit, true ); + + mainMusicPicker->recheckDeletable(); + mainScalePicker->recheckDeletable(); + mainTimbrePicker->recheckDeletable(); + + mAddAction = false; + } + + diff --git a/gameSource/SongEditor.h b/gameSource/SongEditor.h new file mode 100644 index 0000000..8be6d03 --- /dev/null +++ b/gameSource/SongEditor.h @@ -0,0 +1,129 @@ +#ifndef SONG_EDITOR_INCLUDED +#define SONG_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Song.h" +#include "DrawToolSet.h" +#include "SizeLimitedVector.h" +#include "ToolTipSliderGL.h" + + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class SongEditor : public Editor { + + public: + + SongEditor( ScreenGL *inScreen ); + + ~SongEditor(); + + + // may not be saved into picker yet + Song getLiveSong(); + + + // tell editor to re-generate all timbres + void scaleChanged(); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addSong(); + + void saveUndoPoint(); + + + + AddButtonGL *mAddButton; + char mAddAction; + + void setSongToEdit( Song inSong ); + + void refreshMiniView(); + + + MusicCellButtonGL *mButtonGrid[SI][S]; + + Sprite *mEmptyPhraseSprite; + + SpriteButtonGL *mTimbreButtons[SI]; + EditButtonGL *mEditTimbreButtons[SI]; + ToolTipSliderGL *mLoudnessSliders[SI]; + ToolTipSliderGL *mStereoSliders[SI]; + + SpriteButtonGL *mMiniViewButton; + + Song mSongToEdit; + + + EditButtonGL *mEditMusicButton; + + SpriteButtonGL *mEditScaleButton; + + + SelectableButtonGL *mSpeedButtons[3]; + + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + ClearButtonGL *mClearButton; + + + DrawToolSet *mToolSet; + + SelectableButtonGL *mWallsButton; + SelectableButtonGL *mEraseButton; + + // returns true if anything changed + char recursiveFill( int inX, int inY, + uniqueID inOldMusic, uniqueID inNewMusic, + drawTool inTool ); + + + // version for erasing + char recursiveErase( int inX, int inY, + uniqueID inOldMusic, + drawTool inTool ); + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mSetNameField; + + + ToggleSpriteButtonGL *mAddToPackButton; + SpriteButtonGL *mSavePackButton; + + + char mPenDown; + + + int mLastRowTouched; + + char mIgnoreSliders; + + char mFirstSongSet; + + // for usage tracking of phrases in current state + uniqueID mCurrentWorkingPhrasesID; + + // for SEPARATE usage tracking of timbres in current state + uniqueID mCurrentWorkingTimbresID; + }; + +#endif diff --git a/gameSource/SongPicker.cpp b/gameSource/SongPicker.cpp new file mode 100644 index 0000000..a2b9259 --- /dev/null +++ b/gameSource/SongPicker.cpp @@ -0,0 +1,17 @@ +#include "SongPicker.h" + +// all share one stack +SimpleVector globalSongStack; + + +SongPicker::SongPicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalSongStack ) { + } + + + + +SongPicker::~SongPicker() { + + } diff --git a/gameSource/SongPicker.h b/gameSource/SongPicker.h new file mode 100644 index 0000000..16a6fb4 --- /dev/null +++ b/gameSource/SongPicker.h @@ -0,0 +1,40 @@ +#ifndef SONG_PICKER_INCLUDED +#define SONG_PICKER_INCLUDED + +#include "Song.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class SongPicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + SongPicker( double inAnchorX, double inAnchorY ); + + + + virtual ~SongPicker(); + + }; + + + +#endif + + + diff --git a/gameSource/Sprite.cpp b/gameSource/Sprite.cpp new file mode 100644 index 0000000..f3a96c5 --- /dev/null +++ b/gameSource/Sprite.cpp @@ -0,0 +1,249 @@ +#include "Sprite.h" +#include "common.h" + + +#include "minorGems/math/geometry/Angle3D.h" + +#include "minorGems/util/log/AppLog.h" + + + + +Sprite::Sprite( const char *inImageFileName, + char inTransparentLowerLeftCorner, + int inNumFrames, + int inNumPages ) { + Image *spriteImage = readTGA( inImageFileName ); + + if( spriteImage == NULL ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to read image file: %s\n", inImageFileName ); + + // soft fail: default image + spriteImage = new Image( 16, 16, 3, true ); + } + + + initTexture( spriteImage, inTransparentLowerLeftCorner, inNumFrames, + inNumPages ); + + delete spriteImage; + } + + +Sprite::Sprite( Image *inImage, + char inTransparentLowerLeftCorner, + int inNumFrames, + int inNumPages ) { + + initTexture( inImage, inTransparentLowerLeftCorner, inNumFrames, + inNumPages ); + } + + + +void Sprite::initTexture( Image *inImage, + char inTransparentLowerLeftCorner, + int inNumFrames, + int inNumPages ) { + + mNumFrames = inNumFrames; + mNumPages = inNumPages; + + Image *spriteImage = inImage; + + Image *imageToDelete = NULL; + + if( spriteImage->getNumChannels() == 3 && inTransparentLowerLeftCorner ) { + // use lower-left corner color as transparent color for alpha + + Image *fourChannelImage = new Image( spriteImage->getWidth(), + spriteImage->getHeight(), + 4, false ); + + // copy first three + for( int c=0; c<3; c++ ) { + fourChannelImage->pasteChannel( spriteImage->getChannel( c ), + c ); + } + + double *r = fourChannelImage->getChannel( 0 ); + double *g = fourChannelImage->getChannel( 1 ); + double *b = fourChannelImage->getChannel( 2 ); + + // index of transparency + int tI = + fourChannelImage->getWidth() * + ( fourChannelImage->getHeight() - 1 ); + + // color of transparency + double tR = r[tI]; + double tG = g[tI]; + double tB = b[tI]; + + double *alpha = fourChannelImage->getChannel( 3 ); + + int numPixels = + fourChannelImage->getWidth() * + fourChannelImage->getHeight(); + + for( int i=0; igetWidth() / mNumPages; + mBaseScaleY = spriteImage->getHeight() / mNumFrames; + + if( imageToDelete != NULL ) { + delete imageToDelete; + } + + mFlipHorizontal = false; + mHorizontalOffset = 0; + mCurrentPage = 0; + } + + + +Sprite::~Sprite() { + delete mTexture; + } + + + +// opt (found with profiler) +// only construct these once, not every draw call +Vector3D corners[4]; + + + +void Sprite::draw( int inFrame, + double inRotation, Vector3D *inPosition, + double inScale, + double inFadeFactor, + Color *inColor ) { + /* + printf( "Drawing sprite %d, r%f, (%f,%f), s%f, f%f\n", + (int)(this), inRotation, inPosition->mX, inPosition->mY, inScale, + inFadeFactor ); + */ + + // profiler opt: + // this is expensive, and the game never needs it + // (all frame references are specific and never auto-cycling) + // inFrame = inFrame % mNumFrames; + + + double xRadius = inScale * mBaseScaleX / 2; + double yRadius = inScale * mBaseScaleY / 2; + + double xOffset = mHorizontalOffset * inScale; + + + if( mFlipHorizontal ) { + xRadius = -xRadius; + } + + + + // first, set up corners relative to 0,0 + // loop is unrolled here, with all offsets added in + // also, mZ ignored now, since rotation no longer done + double posX = inPosition->mX + xOffset; + double posY = inPosition->mY; + + corners[0].mX = posX - xRadius; + corners[0].mY = posY - yRadius; + //corners[0].mZ = 0; + + corners[1].mX = posX + xRadius; + corners[1].mY = posY - yRadius; + //corners[1].mZ = 0; + + corners[2].mX = posX + xRadius; + corners[2].mY = posY + yRadius; + //corners[2].mZ = 0; + + corners[3].mX = posX - xRadius; + corners[3].mY = posY + yRadius; + //corners[3].mZ = 0; + + // int i; + + + // now rotate around center + // then add inPosition so that center is at inPosition + + // Found with profiler: ignore rotation, since game doesn't use it anyway. + // Angle3D rot( 0, 0, inRotation ); + + // found with profiler: + // unroll this loop + /* + for( i=0; i<4; i++ ) { + corners[i].mX += xOffset; + + // corners[i].rotate( &rot ); + corners[i].add( inPosition ); + } + */ + + if( inColor != NULL ) { + glColor4f( inColor->r, inColor->g, inColor->b, inFadeFactor ); + } + else { + glColor4f( 1, 1, 1, inFadeFactor ); + } + + mTexture->enable(); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + double textXA = (1.0 / mNumPages) * mCurrentPage; + double textXB = textXA + (1.0 / mNumPages ); + + + double textYB = (1.0 / mNumFrames) * inFrame; + double textYA = textYB + (1.0 / mNumFrames ); + + + + glBegin( GL_QUADS ); { + + glTexCoord2f( textXA, textYA ); + glVertex2d( corners[0].mX, corners[0].mY ); + + glTexCoord2f( textXB, textYA ); + glVertex2d( corners[1].mX, corners[1].mY ); + + glTexCoord2f( textXB, textYB ); + glVertex2d( corners[2].mX, corners[2].mY ); + + glTexCoord2f( textXA, textYB ); + glVertex2d( corners[3].mX, corners[3].mY ); + } + glEnd(); + mTexture->disable(); + } + + diff --git a/gameSource/Sprite.h b/gameSource/Sprite.h new file mode 100644 index 0000000..c74c06c --- /dev/null +++ b/gameSource/Sprite.h @@ -0,0 +1,105 @@ +#ifndef SPRITE_INCLUDED +#define SPRITE_INCLUDED + + +#include "minorGems/graphics/openGL/SingleTextureGL.h" +#include "minorGems/graphics/Color.h" + + +#include "minorGems/math/geometry/Vector3D.h" + +#include + + + +class Sprite{ + public: + + // transparent color for RGB images can be taken from lower-left + // corner pixel + // lower-left corner ignored for RGBA images + // image split vertically into inNumFrames of equal size + Sprite( const char *inImageFileName, + char inTransparentLowerLeftCorner = false, + int inNumFrames = 1, + int inNumPages = 1 ); + + + Sprite( Image *inImage, + char inTransparentLowerLeftCorner = false, + int inNumFrames = 1, + int inNumPages = 1 ); + + + ~Sprite(); + + + void draw( int inFrame, + double inRotation, Vector3D *inPosition, + double inScale = 1, + double inFadeFactor = 1, + Color *inColor = NULL ); + + + int getNumFrames() { + return mNumFrames; + } + + + + void setFlipHorizontal( char inFlip ) { + mFlipHorizontal = inFlip; + } + + // used to adjust horizontal center point, in pixels + void setHorizontalOffset( double inOffset ) { + mHorizontalOffset = inOffset; + } + + + void setPage( int inPage ) { + mCurrentPage = inPage; + } + + + int getPage() { + return mCurrentPage; + } + + + + double getBaseScaleX() { + return mBaseScaleX; + } + + double getBaseScaleY() { + return mBaseScaleY; + } + + + protected: + SingleTextureGL *mTexture; + + int mNumFrames; + int mNumPages; + + double mBaseScaleX; + double mBaseScaleY; + + char mFlipHorizontal; + double mHorizontalOffset; + + int mCurrentPage; + + + void initTexture( Image *inImage, + char inTransparentLowerLeftCorner = false, + int inNumFrames = 1, + int inNumPages = 1 ); + + + }; + + + +#endif diff --git a/gameSource/SpriteEditor.cpp b/gameSource/SpriteEditor.cpp new file mode 100644 index 0000000..7f1a3d0 --- /dev/null +++ b/gameSource/SpriteEditor.cpp @@ -0,0 +1,1206 @@ +#include "SpriteEditor.h" +#include "SpritePicker.h" +#include "TilePicker.h" +#include "ColorWells.h" +#include "ColorEditor.h" +#include "BorderPanel.h" +#include "labels.h" +#include "SelectionManager.h" +#include "GridOverlay.h" + + + +extern ColorWells *mainColorStack; +extern SpritePicker *mainSpritePicker; +extern TilePicker *mainTilePicker; + +extern ColorEditor *mainColorEditor; + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + + +template <> +void SizeLimitedVector::deleteElementOfType( + SpriteResource inElement ) { + // no delete necessary + } + + + +SpriteEditor::SpriteEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_sprite" ); + + /* + LabelGL *titleLabel = new LabelGL( 0.75, 0.5, 0.25, + 0.1, "Tile Editor", largeText ); + + + + mSidePanel->add( titleLabel ); + */ + + mSidePanel->add( mainColorStack ); + + mainColorStack->addActionListener( this ); + + + mSidePanel->add( mainSpritePicker ); + + mainSpritePicker->addActionListener( this ); + + + mPickerTransButton = mainSpritePicker->getTransButton(); + + mPickerTransButton->addActionListener( this ); + + + + mEditColorButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 7, + 8, + 8 ); + + mSidePanel->add( mEditColorButton ); + + mEditColorButton->addActionListener( this ); + mEditColorButton->setToolTip( "tip_edit_color" ); + + + mEditPaletteButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 51, + 8, + 8 ); + + mSidePanel->add( mEditPaletteButton ); + + mEditPaletteButton->addActionListener( this ); + mEditPaletteButton->setToolTip( "tip_edit_palette" ); + + /* + // 1-pixel wide white border + // inner panel to provide a 1-pixel wide black gap + + BorderPanel *workingColorBorderPanel = + new BorderPanel( 256, 206, 48, 14, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + 1 ); + + + // smaller to leave black gap + GUIPanelGL *workingColorPanel = new GUIPanelGL( 258, 208, 44, 10, + mWorkingColor ); + + mSidePanel->add( workingColorBorderPanel ); + workingColorBorderPanel->add( workingColorPanel ); + + + mAddButton = new AddButtonGL( 272, 184, 16, 16 ); + mSidePanel->add( mAddButton ); + mAddButton->addActionListener( this ); + + */ + + + double offset = P; + + double buttonSize = (gameHeight - 2 * offset - 8) / P; + + + rgbaColor c = { { 0,0,0,255 } }; + + + + for( int y=0; yadd( mButtonGrid[y][x] ); + + mButtonGrid[y][x]->addActionListener( this ); + } + } + + + + + + mToolSet = new DrawToolSet( 2, 42 ); + mMainPanel->add( mToolSet ); + mToolSet->addActionListener( this ); + + + + mSelectionButton = new SelectableButtonGL( + new Sprite( "selection.tga", true ), + 1, + mToolSet->getAnchorX() + mToolSet->getWidth() + 5, 42, + 20, 20 ); + + mMainPanel->add( mSelectionButton ); + + mSelectionButton->setSelected( false ); + + mSelectionButton->addActionListener( this ); + + mSelectionButton->setToolTip( "tip_selection" ); + + + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "spriteName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + P * buttonSize; + + double extra = gameHeight - gridEdge; + + + // center it vertically on sprite picker + double addY = mainSpritePicker->getAnchorY() + + mainSpritePicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addSprite" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new TwoSpriteButtonGL( + NULL, NULL, + 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + + mTransformToolSet = new TransformToolSet( sideButtonsX, + mMiniViewButton->getAnchorY() + - 8 - 100, + true ) ; + + mMainPanel->add( mTransformToolSet ); + + mTransformToolSet->addActionListener( this ); + + + // old: + // align with grid bottom + double undoButtonY = gameWidth - ( 48 + P * buttonSize ); + + // new + // down more to make room for Trans button + undoButtonY -= 10; + + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + + mEraseButton = new SelectableButtonGL( + new Sprite( "erase.tga", true ), + 1, + sideButtonsX - 2, undoButtonY + 20 + 17, 20, 20 ); + + mMainPanel->add( mEraseButton ); + + mEraseButton->setSelected( false ); + + mEraseButton->addActionListener( this ); + + mEraseButton->setToolTip( "tip_erase" ); + + + + setSpriteToEdit( mainSpritePicker->getSelectedResource() ); + + + mPenDown = false; + + + // listen for tile changes + mainTilePicker->addActionListener( this ); + + + + // fully-opaque gray to avoid color distortions + Color gridColor( 0.25, 0.25, 0.25, 1.0 ); + + GridOverlay *overlay = new GridOverlay( + 8, + gameWidth - ( 48 + P * buttonSize ), + P * buttonSize, P * buttonSize, + P, + gridColor ); + + mMainPanel->add( overlay ); + + + postConstruction(); + } + + + +SpriteEditor::~SpriteEditor() { + mSidePanel->remove( mainColorStack ); + mSidePanel->remove( mainSpritePicker ); + } + + + +void SpriteEditor::setSpriteToEdit( SpriteResource inSprite ) { + mSpriteToEdit = inSprite; + + for( int y=0; ysetColor( mSpriteToEdit.getColor( x, y ) ); + mButtonGrid[y][x]->setHighlight( + mSpriteToEdit.getTrans( x, y ) ); + } + } + refreshMiniView(); + + char *name = mSpriteToEdit.getSpriteName(); + + mNameField->setText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + } + + + +void SpriteEditor::refreshMiniView() { + char useTrans = mainSpritePicker->isTransOn(); + + // don't use cached version + mMiniViewButton->setFrontSprite( mSpriteToEdit.getSprite( useTrans, + false ) ); + + if( useTrans ) { + // backdrop behind sprite when transparency shown + mMiniViewButton->setSprite( + mainTilePicker->getBackgroundTile().getSprite() ); + } + else { + // no backdrop + mMiniViewButton->setSprite( NULL ); + } + } + + + +char SpriteEditor::recursiveFill( int inX, int inY, + rgbaColor inOldColor, + char inOldTrans, + rgbaColor inNewColor, + drawTool inTool ) { + + char fillable = + // filling non-trans area, and colors match + ( !inOldTrans && + ! mSpriteToEdit.getTrans( inX, inY ) && + equal( mSpriteToEdit.getColor( inX, inY ), inOldColor ) ) + + // or filling a trans area, and this pixel is trans + || + ( inOldTrans && mSpriteToEdit.getTrans( inX, inY ) ); + + + char alreadyFilled = + ! mSpriteToEdit.getTrans( inX, inY ) + && + equal( mSpriteToEdit.getColor( inX, inY ), inNewColor ); + + + + if( fillable && ! alreadyFilled ) { + + mButtonGrid[inY][inX]->setColor( inNewColor ); + mSpriteToEdit.editSprite( inX, inY, inNewColor ); + + // always turn trans off wherever user draws with colors + mSpriteToEdit.editTrans( inX, inY, false ); + mButtonGrid[inY][inX]->setHighlight( false ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldColor, inOldTrans, + inNewColor, inTool ); + } + if( inX < P - 1 ) { + recursiveFill( inX + 1, inY, inOldColor, inOldTrans, + inNewColor, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldColor, inOldTrans, + inNewColor, inTool ); + } + if( inY < P - 1 ) { + recursiveFill( inX, inY + 1, inOldColor, inOldTrans, + inNewColor, inTool ); + } + } + + return true; + } + + return false; + } + + + + +char SpriteEditor::recursiveFill( int inX, int inY, + rgbaColor inOldColor, + char inOldTrans, char inNewTrans, + drawTool inTool ) { + + if( equal( mSpriteToEdit.getColor( inX, inY ), inOldColor ) + && + mSpriteToEdit.getTrans( inX, inY ) == inOldTrans + && + mSpriteToEdit.getTrans( inX, inY ) != inNewTrans ) { + + mButtonGrid[inY][inX]->setHighlight( inNewTrans ); + mSpriteToEdit.editTrans( inX, inY, inNewTrans ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldColor, + inOldTrans, inNewTrans, inTool ); + } + if( inX < P - 1 ) { + recursiveFill( inX + 1, inY, inOldColor, + inOldTrans, inNewTrans, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldColor, + inOldTrans, inNewTrans, inTool ); + } + if( inY < P - 1 ) { + recursiveFill( inX, inY + 1, inOldColor, + inOldTrans, inNewTrans, inTool ); + } + } + + return true; + } + + return false; + } + + + + +char SpriteEditor::recursiveSelectionFill( int inX, int inY, + rgbaColor inOldColor, + char inOldTrans, + char inOldSelection, + char inNewSelection, + drawTool inTool ) { + + if( ( equal( mSpriteToEdit.getColor( inX, inY ), inOldColor ) + || + ( inOldTrans && mSpriteToEdit.getTrans( inX, inY ) ) ) + && + SelectionManager::isInSelection( inX, inY ) == inOldSelection + && + SelectionManager::isInSelection( inX, inY ) != inNewSelection ) { + + SelectionManager::toggleSelection( inX, inY, inNewSelection ); + + mButtonGrid[inY][inX]->setSelection( inNewSelection ); + + if( inNewSelection ) { + SelectionManager::setColor( + inX, inY, + mSpriteToEdit.getColor( inX, inY ) ); + SelectionManager::setTrans( + inX, inY, + mSpriteToEdit.getTrans( inX, inY ) ); + } + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveSelectionFill( inX - 1, inY, inOldColor, + inOldTrans, + inOldSelection, inNewSelection, + inTool ); + } + if( inX < P - 1 ) { + recursiveSelectionFill( inX + 1, inY, inOldColor, + inOldTrans, + inOldSelection, inNewSelection, + inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveSelectionFill( inX, inY - 1, inOldColor, + inOldTrans, + inOldSelection, inNewSelection, + inTool ); + } + if( inY < P - 1 ) { + recursiveSelectionFill( inX, inY + 1, inOldColor, + inOldTrans, + inOldSelection, inNewSelection, + inTool ); + } + } + + return true; + } + + return false; + } + + + +void SpriteEditor::toggleErase() { + mEraseButton->setSelected( ! mEraseButton->getSelected() ); + + char showErase = mEraseButton->getSelected(); + + /* + // FIXME + for( int y=0; ysetHighlight( + mSpriteToEdit.getTrans( x, y ) && showTrans ); + } + } + */ + + + if( showErase && mSelectionButton->getSelected() ) { + // only one on at a time + toggleSelection(); + } + + + + // update display of trans in mini view + refreshMiniView(); + } + + + + + + +void SpriteEditor::toggleSelection() { + mSelectionButton->setSelected( ! mSelectionButton->getSelected() ); + + char showSel = mSelectionButton->getSelected(); + + for( int y=0; ysetSelection( + SelectionManager::isInSelection( x, y ) && showSel ); + } + } + + // commit selection only when it is turned on + // (right before it is turned off, the last edits of selection have updated + // the selected colors) + if( showSel ) { + // copy colors + for( int y=0; ygetSelected() ) { + // only one on at a time + toggleErase(); + } + + clearStampOverlay(); + } + + + +void SpriteEditor::clearStampOverlay() { + // turn all overlays off + for( int by=0; by + setOverlay( false ); + } + } + } + + + +void SpriteEditor::colorEditorClosed() { + // it might have been closed by an EditPalette button press + if( mainColorEditor->mEditPalettePressed ) { + showPaletteEditor(); + } + } + + +void SpriteEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainColorStack ) { + // new color picked on stack + + // non-erase mode + if( mEraseButton->getSelected() ) { + toggleErase(); + } + } + else if( inTarget == mainSpritePicker ) { + if( ! mAddAction && + ! mainSpritePicker->wasLastActionFromPress() ) { + + // will change sprite + + mUndoStack.push_back( mSpriteToEdit ); + mUndoButton->setEnabled( true ); + + setSpriteToEdit( mainSpritePicker->getSelectedResource() ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mPickerTransButton ) { + refreshMiniView(); + } + else if( inTarget == mainTilePicker ) { + // tile change... tile used behind sprite transparency + refreshMiniView(); + } + else if( inTarget == mNameField ) { + + mUndoStack.push_back( mSpriteToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mSpriteToEdit.editSpriteName( mNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addSprite(); + } + else if( inTarget == mEditColorButton ) { + mainColorEditor->setEditPaletteButtonVisible( true ); + showColorEditor(); + } + else if( inTarget == mEditPaletteButton ) { + showPaletteEditor(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + SpriteResource last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mSpriteToEdit ); + mRedoButton->setEnabled( true ); + + setSpriteToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + SpriteResource next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mSpriteToEdit ); + mUndoButton->setEnabled( true ); + + setSpriteToEdit( next ); + } + else if( inTarget == mEraseButton ) { + toggleErase(); + } + else if( inTarget == mSelectionButton ) { + toggleSelection(); + } + else if( inTarget == mTransformToolSet ) { + mUndoStack.push_back( mSpriteToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + SpriteResource oldSpriteState = mSpriteToEdit; + + switch( mTransformToolSet->getLastPressed() ) { + case flipH: { + for( int y=0; ysetColor( flipColor ); + + mSpriteToEdit.editSprite( x, y, flipColor ); + mSpriteToEdit.editTrans( x, y, flipTrans ); + } + } + } + break; + case flipV: { + for( int y=0; ysetColor( flipColor ); + + mSpriteToEdit.editSprite( x, y, flipColor ); + mSpriteToEdit.editTrans( x, y, flipTrans ); + } + } + + } + break; + case rotateCCW: { + for( int y=0; ysetColor( flipColor ); + + mSpriteToEdit.editSprite( x, y, flipColor ); + mSpriteToEdit.editTrans( x, y, flipTrans ); + } + } + + } + break; + case rotateCW: { + for( int y=0; ysetColor( flipColor ); + + mSpriteToEdit.editSprite( x, y, flipColor ); + mSpriteToEdit.editTrans( x, y, flipTrans ); + } + } + + } + break; + case clear: { + rgbaColor black; + black.comp.r = 0; + black.comp.g = 0; + black.comp.b = 0; + black.comp.a = 255; + + for( int y=0; ysetColor( black ); + mSpriteToEdit.editSprite( x, y, black ); + mSpriteToEdit.editTrans( x, y, true ); + } + } + } + break; + case colorize: { + rgbaColor c = + mainColorStack->getSelectedColor(); + + for( int y=0; ysetColor( oldColor ); + mSpriteToEdit.editSprite( x, y, oldColor ); + } + } + } + break; + + + } + + + char showTrans = true; + + for( int y=0; ysetHighlight( + mSpriteToEdit.getTrans( x, y ) && showTrans ); + } + } + + refreshMiniView(); + } + else if( inTarget == mToolSet ) { + clearStampOverlay(); + } + else if( !mainColorEditor->getDragging() ){ + // check grid + // but only if not in the middle of drag-picking a color in the editor + + + char found = false; + + for( int y=0; y

    wasLastActionHover() ) { + found = true; + + SpriteResource oldSpriteState = mSpriteToEdit; + char changed = false; + + switch( mToolSet->getSelected() ) { + case pen: { + if( mEraseButton->getSelected() ) { + // add transparency with pen + + if( !mPenDown ) { + // set ink until released + mTransInk = true; + + mPenDown = true; + changed = true; + } + + // use ink already set + mSpriteToEdit.editTrans( x, y, mTransInk ); + + mButtonGrid[y][x]->setHighlight( mTransInk ); + refreshMiniView(); + } + else if( mSelectionButton->getSelected() ) { + // edit selection with pen + + if( !mPenDown ) { + // set ink until released + mSelectionInk = + ! SelectionManager:: + isInSelection( x, y ); + + mPenDown = true; + changed = true; + } + + // use ink already set + SelectionManager::toggleSelection( + x, y, mSelectionInk ); + + mButtonGrid[y][x]->setSelection( + mSelectionInk ); + + if( mSelectionInk ) { + SelectionManager::setColor( + x, y, + mSpriteToEdit.getColor( x, y ) ); + + SelectionManager::setTrans( + x, y, + mSpriteToEdit.getTrans( x, y ) ); + } + } + else { + if( mainColorEditor->isVisible() ) { + // push edited color onto stack first + mainColorEditor->addColor(); + } + + + // edit pixel colors + rgbaColor c = + mainColorStack->getSelectedColor(); + + if( ! equal( + c, + mSpriteToEdit.getColor( x, y ) ) + || + mSpriteToEdit.getTrans( x, y ) ) { + + mButtonGrid[y][x]->setColor( c ); + mSpriteToEdit.editSprite( x, y, c ); + + // always turn trans off wherever user + // draws with colors + mSpriteToEdit.editTrans( x, y, false ); + mButtonGrid[y][x]->setHighlight( false ); + + refreshMiniView(); + + // don't count single pen dots as undoable + // until pen is released + // save undo state only on on initial + // presses + if( !mPenDown ) { + mPenDown = true; + changed = true; + } + } + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + break; + case horLine: + case verLine: + case fill: + if( mEraseButton->getSelected() ) { + // add to trans + if( !mPenDown ) { + mTransInk = true; + } + + changed = recursiveFill( + x, y, + mSpriteToEdit.getColor( x, y ), + mSpriteToEdit.getTrans( x, y ), + mTransInk, + mToolSet->getSelected() ); + + refreshMiniView(); + + if( !mPenDown ) { + mPenDown = true; + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + else if( mSelectionButton->getSelected() ) { + if( !mPenDown ) { + mSelectionInk = ! SelectionManager:: + isInSelection( x, y ); + } + + recursiveSelectionFill( + x, y, + mSpriteToEdit.getColor( x, y ), + mSpriteToEdit.getTrans( x, y ), + SelectionManager:: + isInSelection( x, y ), + mSelectionInk, + mToolSet->getSelected() ); + + if( !mPenDown ) { + mPenDown = true; + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + else { + if( mainColorEditor->isVisible() ) { + // push edited color onto stack first + mainColorEditor->addColor(); + } + + changed = recursiveFill( + x, y, + mSpriteToEdit.getColor( x, y ), + mSpriteToEdit.getTrans( x, y ), + mainColorStack->getSelectedColor(), + mToolSet->getSelected() ); + refreshMiniView(); + } + break; + case pickColor: + if( mSpriteToEdit.getTrans( x, y ) ) { + // trans ink here, switch to erase mode + // thus "picking up" trans ink + + if( ! mEraseButton->getSelected() ) { + toggleErase(); + } + } + else { + // pick up color + mainColorStack->pushColor( + mSpriteToEdit.getColor( x, y ) ); + + // non-erase mode + if( mEraseButton->getSelected() ) { + toggleErase(); + } + } + break; + case stamp: { + if( !mPenDown ) { + changed = true; + mPenDown = true; + } + // insert colors from selection + + intPair center = + SelectionManager::getSelectionCenter(); + + for( int sy=0; sy= 0 && ix >= 0 ) { + rgbaColor c = SelectionManager:: + getColor( sx, sy ); + + char t = SelectionManager:: + getTrans( sx, sy ); + + // always turn trans off wherever + // user draws with colors, + // unless that spot in selection is + // trans + + mButtonGrid[iy][ix]->setColor( c ); + mButtonGrid[iy][ix]->setHighlight( + t ); + + mSpriteToEdit.editSprite( + ix, iy, c ); + mSpriteToEdit.editTrans( + ix, iy, t ); + } + } + } + } + + refreshMiniView(); + + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + break; + } + + + + if( changed ) { + mUndoStack.push_back( oldSpriteState ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + + + } + else if( inTarget == mButtonGrid[y][x] && + mButtonGrid[y][x]->wasLastActionHover() ) { + found = true; + + if( mToolSet->getSelected() == stamp ) { + + // preview of selection that will be inserted + // upon click + + // first, turn all overlays off + for( int by=0; by + setOverlay( false ); + } + } + + intPair center = + SelectionManager::getSelectionCenter(); + + for( int sy=0; sy= 0 && ix >= 0 ) { + rgbaColor c = SelectionManager:: + getColor( sx, sy ); + char t = SelectionManager:: + getTrans( sx, sy ); + + mButtonGrid[iy][ix]-> + setOverlayColor( c ); + mButtonGrid[iy][ix]-> + setOverlayTrans( t ); + + mButtonGrid[iy][ix]-> + setOverlay( true ); + } + } + } + } + + } + + } + + } + } + } + + + + } + + + +void SpriteEditor::editorClosing() { + addSprite(); + } + + + + + +void SpriteEditor::addSprite() { + mAddAction = true; + mSpriteToEdit.finishEdit(); + mainSpritePicker->setSelectedResource( mSpriteToEdit, true ); + mAddAction = false; + } diff --git a/gameSource/SpriteEditor.h b/gameSource/SpriteEditor.h new file mode 100644 index 0000000..815d8e5 --- /dev/null +++ b/gameSource/SpriteEditor.h @@ -0,0 +1,121 @@ +#ifndef SPRITE_EDITOR_INCLUDED +#define SPRITE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "SpriteResource.h" +#include "DrawToolSet.h" +#include "TransformToolSet.h" +#include "SizeLimitedVector.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class SpriteEditor : public Editor { + + public: + + SpriteEditor( ScreenGL *inScreen ); + + ~SpriteEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addSprite(); + + + // override from Editor + virtual void colorEditorClosed(); + + + + AddButtonGL *mAddButton; + char mAddAction; + + void setSpriteToEdit( SpriteResource inSprite ); + + void refreshMiniView(); + + + HighlightColorButtonGL *mButtonGrid[P][P]; + + + TwoSpriteButtonGL *mMiniViewButton; + + SpriteResource mSpriteToEdit; + + + EditButtonGL *mEditColorButton; + EditButtonGL *mEditPaletteButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + TransformToolSet *mTransformToolSet; + + + DrawToolSet *mToolSet; + + SelectableButtonGL *mEraseButton; + + SelectableButtonGL *mSelectionButton; + + + SelectableButtonGL *mPickerTransButton; + + + // returns true if anything changed + char recursiveFill( int inX, int inY, rgbaColor inOldColor, + char inOldTrans, + rgbaColor inNewColor, + drawTool inTool ); + + // version for transparency + char recursiveFill( int inX, int inY, + rgbaColor inOldColor, + char inOldTrans, char inNewTrans, + drawTool inTool ); + + // version for selection + char recursiveSelectionFill( int inX, int inY, + rgbaColor inOldColor, + char inOldTrans, + char inOldSelection, + char inNewSelection, + drawTool inTool ); + + + + void toggleErase(); + void toggleSelection(); + + void clearStampOverlay(); + + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mNameField; + + + char mPenDown; + char mTransInk; + char mSelectionInk; + + }; + +#endif diff --git a/gameSource/SpritePicker.cpp b/gameSource/SpritePicker.cpp new file mode 100644 index 0000000..67ac5f2 --- /dev/null +++ b/gameSource/SpritePicker.cpp @@ -0,0 +1,111 @@ +#include "SpritePicker.h" + +#include "TilePicker.h" + +extern TilePicker *mainTilePicker; + + +// all share one stack +SimpleVector globalSpriteStack; + + +SpritePicker::SpritePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, + &globalSpriteStack ) { + + mainTilePicker->addActionListener( this ); + + + mTransButton = new SelectableButtonGL( + new Sprite( "transSmall.tga", true ), + 1, + mAnchorX + 2 * (mWidth - 12) / 3 + 2, + mAnchorY, + 12, 12 ); + + add( mTransButton ); + + mTransButton->setSelected( true ); + + mTransButton->addActionListener( this ); + + mTransButton->setToolTip( "tip_trans" ); + } + + + + +SpritePicker::~SpritePicker() { + + } + + + + +Sprite *SpritePicker::getButtonGridBackground() { + if( mTransButton->getSelected() ) { + return mainTilePicker->getBackgroundTile().getSprite(); + } + else { + // no trans + return NULL; + } + } + + +void SpritePicker::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mainTilePicker ) { + // tile changed, new background sprite in result display + + // generate new result sprites + getFreshResults(); + + refreshSelectedResourceSprite(); + } + else if( inTarget == mTransButton ) { + // toggle + mTransButton->setSelected( ! mTransButton->getSelected() ); + + getFreshResults(); + + refreshSelectedResourceSprite(); + } + else { + ResourcePicker::actionPerformed( inTarget ); + } + } + + + +void SpritePicker::cloneState( + ResourcePicker *inOtherPicker ) { + + SpritePicker *other = (SpritePicker *)inOtherPicker; + + other->mTransButton->setSelected( mTransButton->getSelected() ); + + other->getFreshResults(); + other->refreshSelectedResourceSprite(); + + + ResourcePicker::cloneState( inOtherPicker ); + } + + + +char SpritePicker::isTransOn() { + return mTransButton->getSelected(); + } + + + +SelectableButtonGL *SpritePicker::getTransButton() { + return mTransButton; + } + + + + + + diff --git a/gameSource/SpritePicker.h b/gameSource/SpritePicker.h new file mode 100644 index 0000000..8cef614 --- /dev/null +++ b/gameSource/SpritePicker.h @@ -0,0 +1,71 @@ +#ifndef SPRITE_PICKER_INCLUDED +#define SPRITE_PICKER_INCLUDED + +#include "SpriteResource.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +#include "buttons.h" + + + +class SpritePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + SpritePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~SpritePicker(); + + + // override this so we can listen to tile-picker too + virtual void actionPerformed( GUIComponent *inTarget ); + + // override to copy trans button state + virtual void cloneState( + ResourcePicker *inOtherPicker ); + + + // gets state of trans button + char isTransOn(); + + // so that others can add action listeners to it + SelectableButtonGL *getTransButton(); + + + + protected: + + + // override, returning current tile as background to show + // behind transparent sprites + Sprite *getButtonGridBackground(); + + // to toggle sprite trans + SelectableButtonGL *mTransButton; + + }; + + + +#endif + + + diff --git a/gameSource/SpriteResource.cpp b/gameSource/SpriteResource.cpp new file mode 100644 index 0000000..2a5caa1 --- /dev/null +++ b/gameSource/SpriteResource.cpp @@ -0,0 +1,531 @@ +#include "SpriteResource.h" +#include "resourceManager.h" +#include "imageCache.h" +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + + +// static inits +SpriteResource *SpriteResource::sBlankSprite = NULL; + +void SpriteResource::staticInit() { + sBlankSprite = new SpriteResource(); + // ensure that blank sprite is saved at least once + sBlankSprite->finishEdit(); + + + /* + printf( "Generating random sprites...\n" ); + + // code to generate lots of random sprites + for( int i=0; i<50000; i++ ) { + if( i%1000 == 0 ) { + printf( "...%d...\n", i ); + } + + SpriteResource r; + r.editSpriteName( "manymany" ); + + // use uniqueID as a random num generator + uniqueID randID = ::makeUniqueID( (unsigned char*)(&i), + sizeof( int ) ); + + + + rgbaColor c; + c.comp.r = randID.bytes[0]; + c.comp.g = randID.bytes[1]; + c.comp.b = randID.bytes[2]; + c.comp.a = 255; + + r.editSprite( randID.bytes[3] % P, + randID.bytes[4] % P, + c ); + + r.finishEdit(); + } + printf( "...done\n" ); + */ + } + +void SpriteResource::staticFree() { + delete sBlankSprite; + } + + + +void SpriteResource::setupDefault() { + + memset( (void *)mPixelColors, 0, mPixelDataLength ); + + // default sprite is fully trans + memset( (void *)mTransFlags, true, mTransDataLength ); + + /* + mPixelColors[0][0].comp.r = 255; + mPixelColors[0][0].comp.a = 255; + + mPixelColors[0][1].comp.g = 255; + mPixelColors[0][1].comp.a = 255; + */ + + const char *defaultName = "default"; + memcpy( mSetName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +SpriteResource::SpriteResource() { + setupDefault(); + } + + + +char SpriteResource::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mPixelDataLength ) { + + memcpy( (void *)mPixelColors, inData, mPixelDataLength ); + + inLength -= mPixelDataLength; + inData = &( inData[ mPixelDataLength ] ); + + + if( inLength >= mTransDataLength ) { + memcpy( (void*)mTransFlags, inData, mTransDataLength ); + + inLength -= mTransDataLength; + + inData = &( inData[mTransDataLength] ); + + // remainder is name + if( inLength <= 11 ) { + memcpy( mSetName, inData, inLength ); + + return true; + } + } + } + return false; + } + + + +SpriteResource::SpriteResource( uniqueID inID, + unsigned char *inData, int inLength ) + : mID( inID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + +SpriteResource::SpriteResource( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "sprite", mID, &length, + &fromNetwork ); + if( data != NULL ) { + + if( !initFromData( data, length ) ) { + // fail + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // load failed + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + + } + + + +void SpriteResource::editSprite( int inX, int inY, rgbaColor inNewColor ) { + mPixelColors[inY][inX] = inNewColor; + } + + +rgbaColor SpriteResource::getColor( int inX, int inY ) { + return mPixelColors[inY][inX]; + } + + + +void SpriteResource::editSpriteName( const char *inName ) { + int newLength = strlen( inName ); + if( newLength > 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Sprite name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mSetName, inName, newLength + 1 ); + } + } + + +void SpriteResource::editTrans( int inX, int inY, char inIsTrans ){ + mTransFlags[inY][inX] = inIsTrans; + } + + +char SpriteResource::getTrans( int inX, int inY ) { + return mTransFlags[inY][inX]; + } + + +#include "minorGems/util/stringUtils.h" + + +char *SpriteResource::getSpriteName() { + return stringDuplicate( mSetName ); + } + + + +#include "minorGems/util/SimpleVector.h" + +void SpriteResource::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + if( ! equal( oldID, mID ) || + ! resourceExists( "sprite", mID ) ) { + + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "sprite", mID, + mSetName, + bytes, numBytes ); + + delete [] bytes; + } + } + + + +unsigned char *SpriteResource::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( (unsigned char *)mPixelColors, + mPixelDataLength ); + + dataAccum.push_back( (unsigned char *)mTransFlags, + mTransDataLength ); + + dataAccum.push_back( (unsigned char *)mSetName, + strlen( mSetName ) + 1 ); + + *outLength = dataAccum.size(); + return dataAccum.getElementArray(); + } + + + +void SpriteResource::saveToPack() { + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + addToPack( "sprite", mID, + mSetName, + bytes, numBytes ); + + delete [] bytes; + } + + + + + +uniqueID SpriteResource::getUniqueID() { + return mID; + } + + + +const char *SpriteResource::getResourceType() { + return "sprite"; + } + +SpriteResource SpriteResource::getDefaultResource() { + return *( SpriteResource::sBlankSprite ); + } + + + + +void SpriteResource::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, + (unsigned char*)mPixelColors, mPixelDataLength ); + p = addToUniqueID( p, + (unsigned char*)mTransFlags, mTransDataLength ); + + p = addToUniqueID( p, (unsigned char*)mSetName, strlen( mSetName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *SpriteResource::getImage( char inUseTrans ) { + Image *image = new Image( P, P, 4, false ); + + double *channels[4]; + + int i; + + for( i=0; i<4; i++ ) { + channels[i] = image->getChannel( i ); + } + + for( int y=0; ycopy(); + + + double *channels[4]; + + int i; + + int numPixels = P * P; + + int opaqueCount = 0; + + double aveComponent = 0; + + + channels[3] = highlight->getChannel( 3 ); + + for( i=0; i<3; i++ ) { + channels[i] = highlight->getChannel( i ); + + for( int p=0; p 0 ) { + + double val = channels[i][p]; + + aveComponent += val; + opaqueCount += 1; + } + } + } + + aveComponent /= opaqueCount; + + double opposite = 0; + + if( aveComponent < 0.5 ) { + opposite = 1; + } + + double same = 1 - opposite; + + + /* + // leave alpha alone + for( i=0; i<3; i++ ) { + channels[i] = highlight->getChannel( i ); + + for( int p=0; p 0.5 ) { + val = 0; + } + else { + val = 1.0; + } + + channels[i][p] = val; + } + } + */ + + // make an outline based on alpha + + char outline[P*P]; + + char edge[P*P]; + + + for( int y=0; y 0 && channels[3][ index - P ] != 0 ) { + outline[index] = 1; + } + else if( y < P-1 && channels[3][ index + P ] != 0 ) { + outline[index] = 1; + } + else if( x > 0 && channels[3][ index - 1 ] != 0 ) { + outline[index] = 1; + } + else if( x < P-1 && channels[3][ index + 1 ] != 0 ) { + outline[index] = 1; + } + } + else { + // opaque + + // does it touch edge? + if( y == 0 || y == P-1 || + x == 0 || x == P-1 ) { + + edge[index] = 1; + } + + + } + + } + } + + + // fill in outline with same color, edges with opposite + for( i=0; i<3; i++ ) { + channels[i] = highlight->getChannel( i ); + + for( int p=0; pfinishEdit(); + } + +void StateObject::staticFree() { + delete sBlankObject; + } + + + +void StateObject::setupDefault() { + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + + makeUniqueID(); + } + + + + +StateObject::StateObject() { + setupDefault(); + } + + +void StateObject::initFromData( unsigned char *inData, int inLength, + char inFromNetwork ) { + + readFromBytes( inData, inLength ); + + if( inFromNetwork ) { + + // sprites might not be present locally, either + + // more efficient to load them in a chunk instead of with separate + // messages + + int numSprites = mSpriteLayers.size(); + + + if( numSprites > 0 ) { + + uniqueID *ids = mSpriteLayers.getElementArray(); + + int *chunkLengths = new int[ numSprites ]; + char *fromNetworkFlags = new char[ numSprites ]; + + + unsigned char **chunks = loadResourceData( "sprite", + numSprites, + ids, + chunkLengths, + fromNetworkFlags ); + + + + for( int i=0; i displacedIDs; + SimpleVector displacedOffsets; + SimpleVector displacedTrans; + SimpleVector displacedGlow; + + int oldSize = mSpriteLayers.size(); + for( int i=thisIndex; i=thisIndex; i--) { + mSpriteLayers.deleteElement( i ); + mLayerOffsets.deleteElement( i ); + mLayerTrans.deleteElement( i ); + mLayerGlow.deleteElement( i ); + } + + + // displaced layers removed + // add new layer to end + mSpriteLayers.push_back( inSpriteID ); + + intPair p = { 0, 0 }; + mLayerOffsets.push_back( p ); + + mLayerTrans.push_back( 255 ); + mLayerGlow.push_back( 0 ); + + + // add displaced after that. + int numDisplaced = displacedIDs.size(); + for( int i=0; i 10 ) { + printf( "Error: StateObject name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + + +char *StateObject::getStateObjectName() { + return stringDuplicate( mName ); + } + + + +// finishes the edit, generates a new unique ID, saves result +void StateObject::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + if( !equal( oldID, mID ) || + ! resourceExists( "object", mID ) ) { + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "object", mID, mName, bytes, numBytes ); + delete [] bytes; + + + // track usages + int numSprites = mSpriteLayers.size(); + + for( int j=0; j= 0 + && + fullX < imageW && fullX >= 0 ) { + + int index = y * P + x; + + Color c = spriteImage->getColor( index ); + + float colorTrans = c.a * layerTrans; + + if( colorTrans > 0 ) { + int fullIndex = fullY * imageW + fullX; + Color oldColor = fullImage.getColor( fullIndex ); + + Color *blend; + + if( oldColor.a > 0 ) { + + if( ! layerGlow ) { + blend = Color::linearSum( + &c, &( oldColor ), + colorTrans ); + } + else { + // additive + blend = oldColor.copy(); + blend->r += c.r * colorTrans; + blend->g += c.g * colorTrans; + blend->b += c.b * colorTrans; + + // cap + if( blend->r > 1 ) { + blend->r = 1; + } + if( blend->g > 1 ) { + blend->g = 1; + } + if( blend->b > 1 ) { + blend->b = 1; + } + } + } + else { + blend = c.copy(); + blend->a = colorTrans; + } + + + // overwrite with blend of ink from higher layer + fullImage.setColor( fullIndex, *blend ); + + delete blend; + + if( fullX < lowX ) { + lowX = fullX; + } + if( fullX > hiX ) { + hiX = fullX; + } + if( fullY < lowY ) { + lowY = fullY; + } + if( fullY > hiY ) { + hiY = fullY; + } + } + } + + } + } + + delete spriteImage; + } + + + int subW = hiX - lowX + 1; + int subH = hiY - lowY + 1; + + Image *subImage = fullImage.getSubImage( lowX, lowY, + subW, subH ); + + + + + //printf( "subimage = x,y=(%d,%d), w,h=(%d,%d)\n", + // lowX, lowY, subW, subH ); + + // build up sums here + Image *shrunkImage = new Image( P, P, 4, true ); + double *subChannels[4]; + double *shrunkChannels[4]; + int hitCount[P*P]; + memset( hitCount, 0, P*P*sizeof(int) ); + + for( int c=0; c<4; c++ ) { + subChannels[c] = subImage->getChannel( c ); + shrunkChannels[c] = shrunkImage->getChannel( c ); + } + + double scaleFactor; + + if( subW > subH ) { + scaleFactor = P / (double)subW; + } + else { + scaleFactor = P / (double)subH; + } + + if( scaleFactor > 1 ) { + // P big enough to contain whole object + scaleFactor = 1; + } + + + // center in shrunk image + int xExtra = (int)( P - ( scaleFactor * subW ) ); + int yExtra = (int)( P - ( scaleFactor * subH ) ); + + int xOffset = xExtra / 2; + int yOffset = yExtra / 2; + + + + for( int y=0; y 0 ) { + + hitCount[shrunkIndex] ++; + + for( int c=0; c<4; c++ ) { + shrunkChannels[c][shrunkIndex] += + subChannels[c][subIndex]; + } + // anything hit by non-trans color is opaque + //shrunkChannels[3][shrunkIndex] = 1; + } + } + } + + for( int c=0; c<4; c++ ) { + for( int i=0; i 0 ) { + shrunkChannels[c][i] /= hitCount[i]; + } + } + } + + // sharp transparency already computed above + // (skipped trans pixels in sums) + + + + delete subImage; + + cachedImage = shrunkImage; + + if( inCacheOK ) { + addCachedImage( mID, inUseTrans, cachedImage ); + } + } + + + + Sprite *sprite = new Sprite( cachedImage ); + + if( !inCacheOK ) { + // image not in cache... we must destroy it + delete cachedImage; + } + + return sprite; + } + + + +const char *StateObject::getResourceType() { + return "object"; + } + + + +StateObject StateObject::getDefaultResource() { + return *sBlankObject; + } + + + + + + +#include "minorGems/util/SimpleVector.h" + +unsigned char *StateObject::makeBytes( int *outLength ) { + SimpleVector buffer; + + buffer.push_back( (unsigned char *)SID_MAGIC_CODE, + strlen( SID_MAGIC_CODE ) ); + + buffer.push_back( objectVersionNumber ); + + + + unsigned char numLayers = (unsigned char)mSpriteLayers.size(); + + // one byte for num layers (max 255 layers) + buffer.push_back( numLayers ); + + for( int i=0; ibytes, U ); + + intPair *p = mLayerOffsets.getElement( i ); + + // one byte each coordinate + buffer.push_back( (unsigned char)p->x ); + buffer.push_back( (unsigned char)p->y ); + + buffer.push_back( *( mLayerTrans.getElement( i ) ) ); + + buffer.push_back( *( mLayerGlow.getElement( i ) ) ); + } + + buffer.push_back( (unsigned char *)mName, strlen( mName ) + 1 ); + + *outLength = buffer.size(); + return buffer.getElementArray(); + } + + + +void StateObject::readFromBytes( unsigned char *inBytes, int inLength ) { + + + unsigned char version = 1; + + // check for magic code before version number + const char *code = SID_MAGIC_CODE; + int codeLength = strlen( code ); + + if( inLength >= codeLength ) { + char codeFound = true; + + + for( int i=0; i= 2 ) { + mLayerTrans.push_back( inBytes[0] ); + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + + if( version >= 3 ) { + mLayerGlow.push_back( inBytes[0] ); + inBytes = &( inBytes[ 1 ] ); + inLength -= 1; + } + else { + // fill in with dummy data + mLayerGlow.push_back( 0 ); + } + } + else { + // fill in with dummy data + mLayerTrans.push_back( 255 ); + } + } + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, inBytes, inLength ); + } + } + + + +void StateObject::makeUniqueID() { + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + mID = ::makeUniqueID( bytes, numBytes ); + delete [] bytes; + } + + + +void StateObject::print() { + char *idString = getHumanReadableString( mID ); + + printf( "StateObject %s:\n", idString ); + + delete [] idString; + + + for( int i=0; ix, p->y, *(mLayerTrans.getElement( i )), + *(mLayerGlow.getElement( i )) ); + delete [] idString; + } + printf( "\n" ); + } diff --git a/gameSource/StateObject.h b/gameSource/StateObject.h new file mode 100644 index 0000000..85fecb3 --- /dev/null +++ b/gameSource/StateObject.h @@ -0,0 +1,137 @@ +#ifndef STATE_OBJECT_INCLUDED +#define STATE_OBJECT_INCLUDED + + +#include "color.h" +#include "common.h" +#include "uniqueID.h" +#include "Sprite.h" + +#include "minorGems/util/SimpleVector.h" + + +class StateObject { + public: + + // empty object with default name + StateObject(); + + + // object loaded from file + StateObject( uniqueID inID ); + + // object loaded from data string + StateObject( uniqueID inID, unsigned char *inData, int inLength, + char inFromNetwork ); + + + int getNumLayers(); + + + // true if there's room for more layers + char canAdd( int inNumToAdd ); + + + // add a new layer with zero offset above layer number "inLayerBelow" + // returns true on success, or false if full + char addLayer( uniqueID inSpriteID, int inLayerBelow = -1 ); + void deleteLayer( int inLayer ); + + void editLayerSprite( int inLayer, uniqueID inSpriteID ); + + void editLayerOffset( int inLayer, intPair inOffset ); + + void editLayerTrans( int inLayer, unsigned char inTrans ); + + void editLayerGlow( int inLayer, char inGlowMode); + + + void moveLayerUp( int inLayer ); + void moveLayerDown( int inLayer ); + + + // name has at most 10 chars + void editStateObjectName( const char *inName ); + + + uniqueID getLayerSprite( int inLayer ); + + intPair getLayerOffset( int inLayer ); + + unsigned char getLayerTrans( int inLayer ); + + char getLayerGlow( int inLayer ); + + + char *getStateObjectName(); + + + + // finishes the edit, generates a new unique ID, saves result + void finishEdit( char inGenerateNewID=true ); + + + // recursively saves to current resource pack + void saveToPack(); + + + + // implements ResourceType functions as needed by ResourcePicker + uniqueID getUniqueID(); + Sprite *getSprite( char inUseTrans=false, char inCacheOK=true ); + static const char *getResourceType(); + static StateObject getDefaultResource(); + + char *getName() { + return getStateObjectName(); + } + + + + + // blank object + static StateObject *sBlankObject; + + static void staticInit(); + static void staticFree(); + + + + void print(); + + + // not part of state that is saved to disk + // used only by editor + int mSelectedLayer; + + + protected: + void setupDefault(); + + void initFromData( unsigned char *inData, int inLength, + char inFromNetwork ); + + + // result destroyed by caller + unsigned char *makeBytes( int *outLength ); + + void readFromBytes( unsigned char *inBytes, int inLength ); + + + void makeUniqueID(); + + + SimpleVector mSpriteLayers; + SimpleVector mLayerOffsets; + SimpleVector mLayerTrans; + SimpleVector mLayerGlow; + + char mName[11]; + + uniqueID mID; + + }; + + + +#endif diff --git a/gameSource/StateObjectDisplay.cpp b/gameSource/StateObjectDisplay.cpp new file mode 100644 index 0000000..de5fd50 --- /dev/null +++ b/gameSource/StateObjectDisplay.cpp @@ -0,0 +1,479 @@ +#include "StateObjectDisplay.h" + +#include "common.h" +#include "SpriteResource.h" +#include "TilePicker.h" + +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/util/stringUtils.h" + + + +extern TilePicker *mainTilePicker; + + + +static clickMask blankMask; + + +StateObjectDisplay::StateObjectDisplay( int inAnchorX, int inAnchorY ) + : GUIComponentGL( inAnchorX, inAnchorY, + G * P, G * P ), + mLastPixelClickX( 0 ), mLastPixelClickY( 0 ), + mLastGridClickX( 0 ), mLastGridClickY( 0 ), + mLastActionRelease( false ), + mLastActionPress( false ), + mMouseHover( false ), + mLastHoverX( 0 ), mLastHoverY( 0 ), + mShowSelected( true ), + mShowGrid( true ), + mUseTrans( true ), + mLastSelected( -2 ), + mBlinkCycle( 0 ) { + + for( int y=0; ygetBackgroundTile() ); + + Sprite *tileSprite = tile.getSprite(); + + + for( int y=0; ydraw( 0, 0, &pos, mZoomFactor ); + } + } + delete tileSprite; + } + + + // now draw all sprites + int numSprites = mObjectSprites.size(); + + //printf( "Drawing %d sprites, %d selected\n", numSprites, selected ); + + for( int s=0; s mAnchorX + mWidth + || + pos.mY < mAnchorY || pos.mY > mAnchorY + mHeight ) { + drawIt = false; + } + + if( drawIt ) { + + char glow = *( mObjectSpriteGlows.getElement( s ) ); + if( glow ) { + // brighten only + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + } + + sprite->draw( 0, 0, &pos, mZoomFactor, fadeFactor, &c ); + + if( glow ) { + // back to normal blend + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + + + if( ! isAnchor && + mShowGrid && + mShowSelected && + s == selected ) { + + + // blinking highlight on top + double blinkFadeFactor = + 0.25 * cos( mBlinkCycle / 3.0 ) + 0.25; + + Sprite *highlight = + *( mObjectSpriteHighlights.getElement( s ) ); + + highlight->draw( 0, 0, &pos, + mZoomFactor, + blinkFadeFactor, &c ); + } + + + } + } + + + + if( mShowGrid ) { + //grid lines + + glColor4f( 1, 1, 1, 0.25f ); + glBegin( GL_LINES ); { + + + for( int x=0; x=0; i-- ) { + intPair pos = *( mObjectSpritePositions.getElement( i ) ); + + int posX = pos.x - centerScreenPosX; + int posY = pos.y - centerScreenPosY; + + //printf( " Layer %d at (%d,%d)\n", i, posX, posY ); + + // first, do bounding box + if( inPixelClickX >= posX && inPixelClickX < posX + P + && + inPixelClickY >= posY && inPixelClickY < posY + P ) { + + clickMask *mask = mObjectMasks.getElement( i ); + + int offsetY = P - ( inPixelClickY - posY ) - 1; + int offsetX = inPixelClickX - posX; + + if( mask->opaque[ offsetY ][ offsetX ] ) { + // hit! + + *outClickOffsetX = offsetX - P/2; + *outClickOffsetY = offsetY - P/2; + return i; + } + } + } + + return -1; + } + diff --git a/gameSource/StateObjectDisplay.h b/gameSource/StateObjectDisplay.h new file mode 100644 index 0000000..c73631f --- /dev/null +++ b/gameSource/StateObjectDisplay.h @@ -0,0 +1,107 @@ +#ifndef STATE_OBJECT_DISPLAY_INCLUDED +#define STATE_OBJECT_DISPLAY_INCLUDED + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" +#include "minorGems/ui/event/ActionListenerList.h" + + +#include "StateObject.h" +#include "common.h" + + + +// used to track trans of each displayed sprite +typedef struct clickMask{ + char opaque[P][P]; + } clickMask; + + + +class StateObjectDisplay : public GUIComponentGL, public ActionListenerList { + + public: + + // sets its own width and height + StateObjectDisplay( int inAnchorX, int inAnchorY ); + + virtual ~StateObjectDisplay(); + + + void setStateObject( StateObject inStateObject, char inUseTrans ); + + // updates sprite positions from data in inState + // inState assumed to be indentical to last setState otherwise + void updateSpritePositions( StateObject inStateObject ); + + // turns grid/anchor drawing on and off + void showGrid( char inShow ); + + + double getZoom(); + void setZoom( double inZoom ); + + + // passes out offset of click insided clicked layer + // offset is from center of sprite layer + int getClickedLayer( int inPixelClickX, int inPixelClickY, + int *outClickOffsetX, int *outClickOffsetY ); + + + + // override + virtual void fireRedraw(); + virtual void mouseDragged( double inX, double inY ); + virtual void mousePressed( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + virtual void mouseMoved( double inX, double inY ); + + + // after this display fires an action, find out + // where the mouse event happened + int mLastPixelClickX, mLastPixelClickY; + int mLastGridClickX, mLastGridClickY; + + char mLastActionRelease; + char mLastActionPress; + + char mMouseHover; + int mLastHoverX, mLastHoverY; + + + // used to disable selected highlight during drags + char mShowSelected; + + protected: + + void setLastMouse( double inX, double inY ); + + double mZoomFactor; + + + char mFocused; + + char mShowGrid; + + + + char mUseTrans; + + int mLastSelected; + int mBlinkCycle; + + + StateObject mStateObject; + + + SimpleVector mObjectSprites; + SimpleVector mObjectSpriteHighlights; + SimpleVector mObjectSpritePositions; + SimpleVector mObjectSpriteFades; + SimpleVector mObjectSpriteGlows; + + SimpleVector mObjectMasks; + + }; + + +#endif diff --git a/gameSource/StateObjectEditor.cpp b/gameSource/StateObjectEditor.cpp new file mode 100644 index 0000000..a254688 --- /dev/null +++ b/gameSource/StateObjectEditor.cpp @@ -0,0 +1,1490 @@ +#include "StateObjectEditor.h" +#include "StateObjectPicker.h" +#include "SpritePicker.h" +#include "SpriteEditor.h" +#include "TilePicker.h" +#include "BorderPanel.h" +#include "DragAndDropManager.h" +#include "labels.h" +#include "ToolTipManager.h" + + +#include "minorGems/util/TranslationManager.h" + + + +#include + + +extern SpritePicker *mainSpritePickerLower; +extern SpritePicker *mainSpritePicker; +extern StateObjectPicker *mainStateObjectPicker; +extern TilePicker *mainTilePicker; + +extern SpriteEditor *mainSpriteEditor; + +extern DragAndDropManager *mainDragAndDrop; + + +extern int gameWidth, gameHeight; + + + +template <> +void SizeLimitedVector::deleteElementOfType( + StateObject inElement ) { + // no delete necessary + } + + + + +StateObjectEditor::StateObjectEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mSelectedLayer( -1 ), + mUndoStack( MAX_UNDOS, false ), + mEditingSelectedLayer( false ), + mDragFromObjectPicker( false ), + mClickStartedOnLayer( false ), + // gen a fake ID + mCurrentWorkingStateID( makeUniqueID( (unsigned char*)"currObject", + strlen( "currObject" ) ) ) { + + AppLog::info( "Constructing state object editor\n" ); + + + mCloseButton->setToolTip( "tip_closeEdit_object" ); + + + mSidePanel->add( mainStateObjectPicker ); + + mainStateObjectPicker->addActionListener( this ); + + + mSidePanel->add( mainSpritePickerLower ); + + mainSpritePickerLower->addActionListener( this ); + + mainSpritePicker->addActionListener( this ); + + + + mStateDisplay = new StateObjectDisplay( 8, gameWidth - 48 - G * P ); + mMainPanel->add( mStateDisplay ); + mStateDisplay->addActionListener( this ); + + + + mEditSpriteButton = + new EditButtonGL( + mainSpritePickerLower->getAnchorX() - 1, + mainSpritePickerLower->getAnchorY() + + mainSpritePickerLower->getHeight() + 1, + 8, + 8 ); + + mSidePanel->add( mEditSpriteButton ); + + mEditSpriteButton->addActionListener( this ); + mEditSpriteButton->setToolTip( "tip_edit_sprite" ); + + + double offset = P; + + double buttonSize = P; + + + + + mGridButton = new SelectableButtonGL( + new Sprite( "grid.tga", true ), + 1, + 72, 42, 20, 20 ); + + mMainPanel->add( mGridButton ); + + mGridButton->setSelected( true ); + + mGridButton->addActionListener( this ); + + mGridButton->setToolTip( "tip_obj_grid" ); + + + + mZoomButton = new SpriteButtonGL( new Sprite( "zoom.tga", true ), + 1, + 8, 44, 16, 16 ); + + mUnZoomButton = new SpriteButtonGL( new Sprite( "unzoom.tga", true ), + 1, + 26, 44, 16, 16 ); + mMainPanel->add( mZoomButton ); + mMainPanel->add( mUnZoomButton ); + mZoomButton->addActionListener( this ); + mUnZoomButton->addActionListener( this ); + + + + + + + mZoomButton->setToolTip( "tip_zoom" ); + mUnZoomButton->setToolTip( "tip_unzoom" ); + + + mTransButton = new SelectableButtonGL( + new Sprite( "trans.tga", true ), + 1, + 50, 42, 20, 20 ); + + mMainPanel->add( mTransButton ); + + mTransButton->setSelected( true ); + + mTransButton->addActionListener( this ); + + mTransButton->setToolTip( "tip_obj_trans" ); + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "objectName" ); + mMainPanel->add( fieldLabel ); + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + G * buttonSize; + + double extra = gameHeight - gridEdge; + + + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + + // center it vertically on room picker + double addY = mainStateObjectPicker->getAnchorY() + + mainStateObjectPicker->getHeight() - 15; + + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addObject" ); + + mAddAction = false; + + + mClearButton = new ClearButtonGL( sideButtonsX, + addY - 20, + 16, 16 ); + mMainPanel->add( mClearButton ); + mClearButton->addActionListener( this ); + + + mCWButton = new RotateCWButtonGL( sideButtonsX, + addY - 40, + 16, 16 ); + mMainPanel->add( mCWButton ); + mCWButton->addActionListener( this ); + mCWButton->setToolTip( "tip_rotateLayer" ); + + + mFlipHButton = new FlipHButtonGL( sideButtonsX, + addY - 60, + 16, 16 ); + mMainPanel->add( mFlipHButton ); + mFlipHButton->addActionListener( this ); + mFlipHButton->setToolTip( "tip_flipLayer" ); + + + + Color *thumbColor = new Color( .5, .5, .5, .5 ); + Color *borderColor = new Color( .35, .35, .35, .35 ); + + + mLayerTransSlider = new ToolTipSliderGL( sideButtonsX - 1 - 3, addY - 80 + 9, + 16, 10, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mLayerTransSlider ); + mLayerTransSlider->setThumbPosition( 1.0 ); + mLayerTransSlider->addActionListener( this ); + mLayerTransSlider->setToolTip( "tip_fadeLayer" ); + + + delete thumbColor; + delete borderColor; + + + + + double layerButtonSize = P + 4; + + mObjectLayerViewButton = new TwoSpriteButtonGL( + NULL, NULL, 1, + 108, 42, + layerButtonSize, + layerButtonSize ); + + mMainPanel->add( mObjectLayerViewButton ); + + + + mObjectPrevLayerButton = new LeftButtonGL( + mObjectLayerViewButton->getAnchorX() - 8, + mObjectLayerViewButton->getAnchorY() + layerButtonSize - 8, + 8, + 8 ); + mMainPanel->add( mObjectPrevLayerButton ); + + mObjectPrevLayerButton->addActionListener( this ); + + mObjectPrevLayerButton->setToolTip( "tip_prev_layer" ); + + + mObjectNextLayerButton = new RightButtonGL( + mObjectLayerViewButton->getAnchorX() + layerButtonSize, + mObjectLayerViewButton->getAnchorY() + layerButtonSize - 8, + 8, + 8 ); + mMainPanel->add( mObjectNextLayerButton ); + + mObjectNextLayerButton->addActionListener( this ); + + mObjectNextLayerButton->setToolTip( "tip_next_layer" ); + + + + static rgbaColor black = {{0,0,0,255}}; + + mObjectBottomLayerButton = + new SpriteButtonGL( new Sprite( "first.tga", true ), + 1, + mObjectPrevLayerButton->getAnchorX(), + mObjectPrevLayerButton->getAnchorY() - 12, + 8, 8 ); + mObjectBottomLayerButton->setBorderColor( black ); + + mMainPanel->add( mObjectBottomLayerButton ); + + mObjectBottomLayerButton->addActionListener( this ); + + mObjectBottomLayerButton->setToolTip( "tip_bottom_layer" ); + + + + mObjectTopLayerButton = + new SpriteButtonGL( new Sprite( "last.tga", true ), + 1, + mObjectNextLayerButton->getAnchorX(), + mObjectNextLayerButton->getAnchorY() - 12, + 8, 8 ); + mObjectTopLayerButton->setBorderColor( black ); + + mMainPanel->add( mObjectTopLayerButton ); + + mObjectTopLayerButton->addActionListener( this ); + + mObjectTopLayerButton->setToolTip( "tip_top_layer" ); + + + + mLayerUpButton = + new SpriteButtonGL( new Sprite( "layerUp.tga", true ), + 1, + mObjectNextLayerButton->getAnchorX() + 10, + mObjectNextLayerButton->getAnchorY() - 1, + 8, 8 ); + mLayerUpButton->setBorderColor( black ); + + mMainPanel->add( mLayerUpButton ); + + mLayerUpButton->addActionListener( this ); + + mLayerUpButton->setToolTip( "tip_layer_up" ); + + mLayerDownButton = + new SpriteButtonGL( new Sprite( "layerDown.tga", true ), + 1, + mObjectNextLayerButton->getAnchorX() + 10, + mObjectNextLayerButton->getAnchorY() - 11, + 8, 8 ); + mLayerDownButton->setBorderColor( black ); + + mMainPanel->add( mLayerDownButton ); + + mLayerDownButton->addActionListener( this ); + + mLayerDownButton->setToolTip( "tip_layer_down" ); + + + + + + mLayerGlowButton = new ToggleSpriteButtonGL( + new Sprite( "glow.tga", true ), + new Sprite( "noGlow.tga", true ), + 1, + mLayerTransSlider->getAnchorX() + mLayerTransSlider->getWidth(), + mLayerTransSlider->getAnchorY() + 1, + 8, 8 ); + + + mLayerGlowButton->setToolTip( "tip_glowLayer" ); + mLayerGlowButton->setSecondToolTip( "tip_noGlowLayer" ); + + mMainPanel->add( mLayerGlowButton ); + + mLayerGlowButton->addActionListener( this ); + + + + double replaceY = mainSpritePickerLower->getAnchorY() + + mainSpritePickerLower->getHeight() - 15; + + mReplaceSpriteButton = new SpriteButtonGL( + new Sprite( "replaceSprite.tga", true ), + 1, + sideButtonsX, replaceY, 16, 16 ); + + mMainPanel->add( mReplaceSpriteButton ); + + mReplaceSpriteButton->addActionListener( this ); + + mReplaceSpriteButton->setToolTip( "tip_replace_layer" ); + + + // reuse object add sprite + mAddSpriteButton = new KeyEquivButtonGL( + new Sprite( "addObject.tga", true ), + 1, + sideButtonsX, replaceY - 20, 16, 16, 'a', 'A' ); + + mMainPanel->add( mAddSpriteButton ); + + mAddSpriteButton->addActionListener( this ); + + mAddSpriteButton->setToolTip( "tip_new_layer" ); + + + + mEditSelectedSpriteButton = new KeyEquivButtonGL( + new Sprite( "editSprite.tga", true ), + 1, + sideButtonsX, replaceY + 40, 16, 16, 'e', 'E' ); + + mMainPanel->add( mEditSelectedSpriteButton ); + + mEditSelectedSpriteButton->addActionListener( this ); + + mEditSelectedSpriteButton->setToolTip( "tip_edit_layer" ); + + + mRemoveSpriteButton = new KeyEquivButtonGL( + new Sprite( "removeSprite.tga", true ), + 1, + sideButtonsX, replaceY + 20, 16, 16, + 'x', 'X' ); + + mMainPanel->add( mRemoveSpriteButton ); + + mRemoveSpriteButton->addActionListener( this ); + + mRemoveSpriteButton->setToolTip( "tip_delete_layer" ); + + + // obj 0 starts out selected, can't be deleted or edited + mRemoveSpriteButton->setEnabled( false ); + mEditSelectedSpriteButton->setEnabled( false ); + + + + + + double undoButtonY = gameWidth - ( 48 + offset + (G - 1) * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + mIgnoreSliders = false; + mUndoOrRedoAction = false; + + + setStateObjectToEdit( mStateObjectToEdit ); + + + // start one step zoomed in (can zoom in and out) + mStateDisplay->setZoom( 2 ); + mZoomButton->setEnabled( true ); + mUnZoomButton->setEnabled( true ); + + + + mNoDropImage = readTGA( "noDrop.tga" ); + mCanDropImage = readTGA( "canDrop.tga" ); + + + + mainTilePicker->addActionListener( this ); + + + + // dnd on top of side panel + postConstructionSide(); + + mMainPanel->add( mainDragAndDrop ); + + // but *below* special main panel widgets + postConstructionMain(); + } + + + +StateObjectEditor::~StateObjectEditor() { + mSidePanel->remove( mainSpritePickerLower ); + mSidePanel->remove( mainStateObjectPicker ); + + mMainPanel->remove( mainDragAndDrop ); + + delete mNoDropImage; + delete mCanDropImage; + } + + + +void StateObjectEditor::setStateObjectToEdit( StateObject inStateObject ) { + + // clear old working usages + removeUsages( mCurrentWorkingStateID ); + + + mStateObjectToEdit = inStateObject; + + //mainRoomPicker->setSelectedResource( mStateObjectToEdit->mRoom ); + + mStateDisplay->setStateObject( mStateObjectToEdit, + mTransButton->getSelected() ); + + + //int selectedObj = mStateObjectToEdit->getSelectedObject(); + //int selectedLayer = mStateObjectToEdit->getSelectedLayer(); + + int numLayers = mStateObjectToEdit.getNumLayers(); + + if( mSelectedLayer >= numLayers ) { + mSelectedLayer = numLayers - 1; + } + + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + + + + mRemoveSpriteButton->setEnabled( true ); + mReplaceSpriteButton->setEnabled( true ); + mEditSelectedSpriteButton->setEnabled( true ); + if( mSelectedLayer == -1 ) { + // can't manipulate anchor + mRemoveSpriteButton->setEnabled( false ); + mReplaceSpriteButton->setEnabled( false ); + mEditSelectedSpriteButton->setEnabled( false ); + } + + + char useTrans = mTransButton->getSelected(); + + if( useTrans ) { + // backdrop behind sprite when transparency shown + mObjectLayerViewButton->setSprite( + mainTilePicker->getBackgroundTile().getSprite() ); + } + else { + // no backdrop + mObjectLayerViewButton->setSprite( NULL ); + } + + + if( mSelectedLayer == -1 ) { + // display anchor + mObjectLayerViewButton->setFrontSprite( + new Sprite( "anchor.tga", true ) ); + + mObjectPrevLayerButton->setEnabled( false ); + mCWButton->setToolTip( "tip_rotateWholeObject" ); + mFlipHButton->setToolTip( "tip_flipWholeObject" ); + mLayerTransSlider->setToolTip( "tip_fadeWholeObject" ); + + mLayerGlowButton->setToolTip( "tip_glowWholeObject" ); + mLayerGlowButton->setSecondToolTip( "tip_noGlowWholeObject" ); + + + mObjectNextLayerButton->setEnabled( + mStateObjectToEdit.getNumLayers() > 0 ); + + + mObjectBottomLayerButton->setEnabled( false ); + + // don't show if redundant + mObjectTopLayerButton->setEnabled( + mStateObjectToEdit.getNumLayers() > 1 ); + + mLayerUpButton->setEnabled( false ); + mLayerDownButton->setEnabled( false ); + } + else { + // display selected sprite + SpriteResource resource( + mStateObjectToEdit.getLayerSprite( mSelectedLayer ) ); + + mObjectLayerViewButton->setFrontSprite( + resource.getSprite( useTrans ) ); + + mObjectPrevLayerButton->setEnabled( true ); + mCWButton->setToolTip( "tip_rotateLayer" ); + mFlipHButton->setToolTip( "tip_flipLayer" ); + mLayerTransSlider->setToolTip( "tip_fadeLayer" ); + + mLayerGlowButton->setToolTip( "tip_glowLayer" ); + mLayerGlowButton->setSecondToolTip( "tip_noGlowLayer" ); + + + mObjectNextLayerButton->setEnabled( + mStateObjectToEdit.getNumLayers() - 1 > mSelectedLayer ); + + + + // don't show if redundant + mObjectBottomLayerButton->setEnabled( mSelectedLayer > 0 ); + + mObjectTopLayerButton->setEnabled( + mStateObjectToEdit.getNumLayers() - 2 > mSelectedLayer ); + + + mLayerUpButton->setEnabled( mObjectNextLayerButton->isEnabled() ); + mLayerDownButton->setEnabled( mObjectBottomLayerButton->isEnabled() ); + } + + + char *name = mStateObjectToEdit.getStateObjectName(); + + mNameField->setText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + + + if( mSelectedLayer >= 0 ) { + // auto-change selected sprite in picker + SpriteResource resource( + mStateObjectToEdit.getLayerSprite( mSelectedLayer) ); + + mainSpritePickerLower->setSelectedResource( resource ); + } + + mIgnoreSliders = true; + + if( mSelectedLayer >= 0 ) { + mLayerTransSlider->setEnabled( true ); + + // only 32 pixels in slider... avoid round-off errors + mLayerTransSlider->setThumbPosition( + (int)( mStateObjectToEdit.getLayerTrans( mSelectedLayer ) + / 255.0 * 32 ) / 32 ); + + mLayerGlowButton->setState( + mStateObjectToEdit.getLayerGlow( mSelectedLayer ) ); + } + else { + + mLayerTransSlider->setThumbPosition( 1 ); + + // show glow button default to on or off for whole object? + + int countGlowing = 0; + + int numLayers = mStateObjectToEdit.getNumLayers(); + + for( int i=0; isetState( countGlowing > numLayers / 2 ); + } + + + mIgnoreSliders = false; + + + + mAddSpriteButton->setEnabled( mStateObjectToEdit.canAdd( 1 ) ); + + + // no longer valid + mWholeDragOffsets.deleteAll(); + + + // add usages for current working object + for( int i=0; irecheckDeletable(); + mainSpritePickerLower->recheckDeletable(); + } + + +void StateObjectEditor::clearRedoStack() { + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + +void StateObjectEditor::saveUndoPoint() { + mUndoStack.push_back( mStateObjectToEdit ); + mUndoButton->setEnabled( true ); + + // new branch, old redo future impossible + clearRedoStack(); + } + + + +void StateObjectEditor::generateDraggingOffsets() { + mWholeDragOffsets.deleteAll(); + + int numLayers = mStateObjectToEdit.getNumLayers(); + + for( int i=0; iwasLastActionFromPress() && + this->isVisible() ) { + + SpriteResource resource = + mainSpritePickerLower->getDraggedResource(); + + intPair zeroOffset = { 0, 0 }; + + + intPair *offsets = new intPair[ 2 ]; + Sprite **sprites = new Sprite*[ 2 ]; + float *trans = new float[ 2 ]; + char *glows = new char[ 2 ]; + + offsets[0] = zeroOffset; + sprites[0] = resource.getSprite( false, true ); + trans[0] = 1.0f; + glows[0] = false; + + offsets[1] = zeroOffset; + trans[1] = 0.5f; + glows[1] = false; + + if( !mStateObjectToEdit.canAdd( 1 ) ) { + sprites[1] = new Sprite( mNoDropImage, true ); + + ToolTipManager::setTip( + (char*)TranslationManager::translate( "tip_objectFull" ) ); + } + else { + sprites[1] = new Sprite( mCanDropImage, true ); + + ToolTipManager::setTip( + (char*)TranslationManager::translate( + "tip_draggingSprite" ) ); + } + ToolTipManager::freeze( true ); + + mainDragAndDrop->setSprites( 2, sprites, offsets, + trans, glows, + mStateDisplay->getZoom() ); + mDragFromObjectPicker = false; + } + } + else if( inTarget == mainSpritePicker ) { + + if( mEditingSelectedLayer && + mainSpriteEditor->isVisible() ) { + + // auto-replace + saveUndoPoint(); + + SpriteResource resource = + mainSpritePicker->getSelectedResource(); + + + mStateObjectToEdit.editLayerSprite( mSelectedLayer, + resource.getUniqueID() ); + + setStateObjectToEdit( mStateObjectToEdit ); + } + } + else if( inTarget == mainStateObjectPicker ) { + + if( mainStateObjectPicker->wasLastActionFromPress() && + this->isVisible() ) { + StateObject resource = + mainStateObjectPicker->getDraggedResource(); + + int numSprites = resource.getNumLayers(); + + + int numTotalSprites = numSprites + 1; + + intPair *offsets = new intPair[ numTotalSprites ]; + Sprite **sprites = new Sprite*[ numTotalSprites ]; + float *trans = new float[ numTotalSprites ]; + char *glows = new char[ numTotalSprites ]; + + for( int i=0; isetSprites( numTotalSprites, sprites, offsets, + trans, glows, + mStateDisplay->getZoom() ); + mDragFromObjectPicker = true; + } + // ignore if caused by our own Add action + else if( ! mAddAction && + ! mainStateObjectPicker->wasLastActionFromPress() ) { + + // will change object + + mUndoStack.push_back( mStateObjectToEdit ); + mUndoButton->setEnabled( true ); + + StateObject resource = + mainStateObjectPicker->getSelectedResource(); + + int numLayers = resource.getNumLayers(); + + // always jump to top layer + mSelectedLayer = numLayers - 1; + resource.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( resource ); + + mainSpritePickerLower->recheckDeletable(); + mainSpritePicker->recheckDeletable(); + + // new branch... "redo" future now impossible + clearRedoStack(); + } + } + else if( inTarget == mainTilePicker ) { + // tile change... tile used behind sprite transparency + + // don't refresh entire object, no need to! + char useTrans = mTransButton->getSelected(); + + if( useTrans ) { + // backdrop behind sprite when transparency shown + mObjectLayerViewButton->setSprite( + mainTilePicker->getBackgroundTile().getSprite() ); + } + } + else if( inTarget == mNameField ) { + + mUndoStack.push_back( mStateObjectToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + clearRedoStack(); + + mStateObjectToEdit.editStateObjectName( mNameField->getText() ); + } + else if( inTarget == mEditSpriteButton ) { + mEditingSelectedLayer = false; + showSpriteEditor(); + } + else if( inTarget == mEditSelectedSpriteButton ) { + mEditingSelectedLayer = true; + + // make sure selected one is chosen in picker + setStateObjectToEdit( mStateObjectToEdit ); + + showSpriteEditor(); + } + else if( inTarget == mUndoButton ) { + mUndoOrRedoAction = true; + + int lastIndex = mUndoStack.size() - 1; + + StateObject last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mStateObjectToEdit ); + mRedoButton->setEnabled( true ); + + // restore selected layer + mSelectedLayer = last.mSelectedLayer; + + setStateObjectToEdit( last ); + + mUndoOrRedoAction = false; + } + else if( inTarget == mRedoButton ) { + mUndoOrRedoAction = true; + + int nextIndex = mRedoStack.size() - 1; + + StateObject next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mStateObjectToEdit ); + mUndoButton->setEnabled( true ); + + // restore selected layer + mSelectedLayer = next.mSelectedLayer; + + setStateObjectToEdit( next ); + + mUndoOrRedoAction = false; + } + else if( inTarget == mAddButton ) { + addStateObject(); + } + else if( inTarget == mAddSpriteButton ) { + saveUndoPoint(); + + SpriteResource resource = mainSpritePickerLower->getSelectedResource(); + + char added = mStateObjectToEdit.addLayer( + resource.getUniqueID(), mSelectedLayer ); + + if( added ) { + // select newly-added layer + mSelectedLayer ++; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + if( mStateDisplay->mMouseHover ) { + // "button press" while over display (not over button) + // must be Ctrl-a keyboard shortcut + // Add sprite at mouse location on display + intPair offset = + { mStateDisplay->mLastHoverX , + mStateDisplay->mLastHoverY }; + + mStateObjectToEdit.editLayerOffset( + mSelectedLayer, + offset ); + } + // else leave with 0 offsets, centered + } + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mRemoveSpriteButton ) { + saveUndoPoint(); + + if( mSelectedLayer >= 0 ) { + mStateObjectToEdit.deleteLayer( mSelectedLayer ); + + int numLayersLeft = mStateObjectToEdit.getNumLayers(); + + if( mSelectedLayer >= numLayersLeft ) { + mSelectedLayer --; + } + } + + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mReplaceSpriteButton ) { + saveUndoPoint(); + + SpriteResource resource = mainSpritePickerLower->getSelectedResource(); + + + mStateObjectToEdit.editLayerSprite( mSelectedLayer, + resource.getUniqueID() ); + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mObjectNextLayerButton ) { + mSelectedLayer ++; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mObjectPrevLayerButton ) { + mSelectedLayer --; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mObjectBottomLayerButton ) { + mSelectedLayer = -1; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mObjectTopLayerButton ) { + mSelectedLayer = mStateObjectToEdit.getNumLayers() - 1; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mLayerUpButton ) { + saveUndoPoint(); + + mStateObjectToEdit.moveLayerUp( mSelectedLayer ); + mSelectedLayer ++; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mLayerDownButton ) { + saveUndoPoint(); + + mStateObjectToEdit.moveLayerDown( mSelectedLayer ); + mSelectedLayer --; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mTransButton ) { + // toggle + mTransButton->setSelected( ! mTransButton->getSelected() ); + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mGridButton ) { + // toggle + mGridButton->setSelected( ! mGridButton->getSelected() ); + + mStateDisplay->showGrid( mGridButton->getSelected() ); + } + else if( inTarget == mZoomButton ) { + mStateDisplay->setZoom( mStateDisplay->getZoom() + 1 ); + mUnZoomButton->setEnabled( true ); + if( mStateDisplay->getZoom() > 2 ) { + mZoomButton->setEnabled( false ); + } + } + else if( inTarget == mUnZoomButton ) { + mStateDisplay->setZoom( mStateDisplay->getZoom() - 1 ); + mZoomButton->setEnabled( true ); + if( mStateDisplay->getZoom() == 1 ) { + mUnZoomButton->setEnabled( false ); + } + } + else if( inTarget == mClearButton ) { + saveUndoPoint(); + + int numLayers = mStateObjectToEdit.getNumLayers(); + + for( int i=numLayers-1; i>=0; i-- ) { + mStateObjectToEdit.deleteLayer( i ); + } + mSelectedLayer = -1; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( inTarget == mCWButton ) { + saveUndoPoint(); + + if( mSelectedLayer >= 0 ) { + // rotate just one layer + + uniqueID id = mStateObjectToEdit.getLayerSprite( mSelectedLayer ); + + SpriteResource oldSpriteState( id ); + + SpriteResource newSpriteState( id ); + + for( int y=0; y= 0 ) { + // flip just one layer + + uniqueID id = mStateObjectToEdit.getLayerSprite( mSelectedLayer ); + + SpriteResource oldSpriteState( id ); + + SpriteResource newSpriteState( id ); + + for( int y=0; ymJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + if( mSelectedLayer >= 0 ) { + // just current layer + mStateObjectToEdit.editLayerTrans( + mSelectedLayer, + (unsigned char)( + 255 * mLayerTransSlider->getThumbPosition() ) ); + } + else { + // all layers together + int numLayers = mStateObjectToEdit.getNumLayers(); + + for( int i=0; igetThumbPosition() ) ); + } + } + + + // don't redo sprites + mStateDisplay->updateSpritePositions( mStateObjectToEdit ); + } + else if( inTarget == mLayerGlowButton ) { + if( mLayerTransSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + char glow = mLayerGlowButton->getState(); + + if( mSelectedLayer >= 0 ) { + // just current layer + mStateObjectToEdit.editLayerGlow( mSelectedLayer, glow ); + } + else { + // all layers together + int numLayers = mStateObjectToEdit.getNumLayers(); + + for( int i=0; iupdateSpritePositions( mStateObjectToEdit ); + } + else if( inTarget == mStateDisplay && + ! mLayerTransSlider->mDragging ) { + // ignore events caused by off-slider draggin + + + // selected layer change? + if( mStateDisplay->mLastActionPress ) { + int clickedLayer = mStateDisplay->getClickedLayer( + mStateDisplay->mLastPixelClickX, + mStateDisplay->mLastPixelClickY, + &( mLayerClickOffset.x ), + &( mLayerClickOffset.y ) ); + + if( clickedLayer != -1 ) { + if( clickedLayer != mSelectedLayer ) { + // new layer hit! + + mSelectedLayer = clickedLayer; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + setStateObjectToEdit( mStateObjectToEdit ); + } + mClickStartedOnLayer = true; + } + else if( clickedLayer == -1 ) { + // same layer again, but clicking away from it + // (causes it to jump) + + // center selected layer on mouse + mLayerClickOffset.x = 0; + mLayerClickOffset.y = 0; + + mClickStartedOnLayer = false; + } + } + + if( mStateDisplay->mLastActionRelease && + mainDragAndDrop->isDragging() ) { + + // add new layer (or layers, of obj dragged) + + saveUndoPoint(); + + if( mDragFromObjectPicker ) { + StateObject resource = + mainStateObjectPicker->getDraggedResource(); + + int numNewLayers = resource.getNumLayers(); + + if( mStateObjectToEdit.canAdd( numNewLayers ) ) { + + + for( int i=0; imLastPixelClickX + + layerOffset.x, + mStateDisplay->mLastPixelClickY + + layerOffset.y + 1 }; + + mStateObjectToEdit.editLayerOffset( + mSelectedLayer, + offset ); + + mStateObjectToEdit.editLayerTrans( + mSelectedLayer, + resource.getLayerTrans( i ) ); + } + } + } + } + else { + SpriteResource resource = + mainSpritePickerLower->getDraggedResource(); + + if( mStateObjectToEdit.canAdd( 1 ) ) { + + char added = mStateObjectToEdit.addLayer( + resource.getUniqueID(), + mSelectedLayer ); + + if( added ) { + // select newly-added layer + mSelectedLayer ++; + mStateObjectToEdit.mSelectedLayer = mSelectedLayer; + + // position it properly based on location of drop + intPair offset = + { mStateDisplay->mLastPixelClickX, + mStateDisplay->mLastPixelClickY + 1 }; + + mStateObjectToEdit.editLayerOffset( mSelectedLayer, + offset ); + } + } + } + + setStateObjectToEdit( mStateObjectToEdit ); + } + else if( ! mainDragAndDrop->isDragging() ) { + // move layer (or all layers) + + if( ! mStateDisplay->mLastActionRelease && + ! mStateDisplay->mLastActionPress ) { + // hide selected border during drag for precise positioning + mStateDisplay->mShowSelected = false; + } + + + // only save an undo point when mouse initially pressed + // (ignore micro-state changes until release) + if( mStateDisplay->mLastActionPress ) { + + + if( mSelectedLayer == -1 ) { + + // save all relative offsets + generateDraggingOffsets(); + + // move relative to first click + mLayerClickOffset.x = mStateDisplay->mLastPixelClickX; + mLayerClickOffset.y = mStateDisplay->mLastPixelClickY; + + // whole-object moving: doesn't matter if click started + // on a layer + saveUndoPoint(); + } + else { + // some layer selected + // only save undo point if click started on a layer, + // else nothing is going to move + if( mClickStartedOnLayer ) { + saveUndoPoint(); + } + } + } + + if( mSelectedLayer == -1 ) { + // whole object, move all + + intPair offset = + { mStateDisplay->mLastPixelClickX - mLayerClickOffset.x, + mStateDisplay->mLastPixelClickY - + mLayerClickOffset.y }; + + int numLayers = mStateObjectToEdit.getNumLayers(); + + if( mWholeDragOffsets.size() != numLayers ) { + // our offsets aren't set up properly + // maybe this drag started with an off-display click? + generateDraggingOffsets(); + } + + + for( int i=0; imLastPixelClickX - mLayerClickOffset.x, + mStateDisplay->mLastPixelClickY + + mLayerClickOffset.y + 1 }; + + mStateObjectToEdit.editLayerOffset( mSelectedLayer, + offset ); + + // no longer valid + mWholeDragOffsets.deleteAll(); + } + + // no need to generate all new sprites + mStateDisplay->updateSpritePositions( mStateObjectToEdit ); + } + } + } + + + +void StateObjectEditor::editorClosing() { + addStateObject(); + } + + + +void StateObjectEditor::addStateObject() { + mAddAction = true; + mStateObjectToEdit.finishEdit(); + mainStateObjectPicker->setSelectedResource( mStateObjectToEdit, true ); + + mainSpritePickerLower->recheckDeletable(); + mainSpritePicker->recheckDeletable(); + + + mAddAction = false; + } + + + diff --git a/gameSource/StateObjectEditor.h b/gameSource/StateObjectEditor.h new file mode 100644 index 0000000..8f267df --- /dev/null +++ b/gameSource/StateObjectEditor.h @@ -0,0 +1,155 @@ +#ifndef STATE_OBJECT_EDITOR_INCLUDED +#define STATE_OBJECT_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "StateObject.h" +#include "StateObjectDisplay.h" +#include "SizeLimitedVector.h" +#include "ToolTipSliderGL.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class StateObjectEditor : public Editor { + + public: + + StateObjectEditor( ScreenGL *inScreen ); + + ~StateObjectEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addStateObject(); + + + void generateDraggingOffsets(); + + + + AddButtonGL *mAddButton; + char mAddAction; + + + int mSelectedLayer; + + // where user clicked on layer, move layer center relative to + // mouse movement using this offset + intPair mLayerClickOffset; + + + // when dragging the whole object around, track offset of each layer + SimpleVector mWholeDragOffsets; + + + + void setStateObjectToEdit( StateObject inStateObject ); + + StateObject mStateObjectToEdit; + + + StateObjectDisplay *mStateDisplay; + + + //EditButtonGL *mEditRoomButton; + EditButtonGL *mEditSpriteButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + ClearButtonGL *mClearButton; + + RotateCWButtonGL *mCWButton; + FlipHButtonGL *mFlipHButton; + + ToolTipSliderGL *mLayerTransSlider; + + char mIgnoreSliders; + + + char mUndoOrRedoAction; + + void clearRedoStack(); + + void saveUndoPoint(); + + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + + + + + //QuickDeleteButtonGL *mObjectLayerDeleteButton; + + //SmallAddButtonGL *mObjectLayerAddButton; + //SpriteButtonGL *mLayerReplaceButton; + + + SpriteButtonGL *mReplaceSpriteButton; + KeyEquivButtonGL *mAddSpriteButton; + KeyEquivButtonGL *mEditSelectedSpriteButton; + KeyEquivButtonGL *mRemoveSpriteButton; + + + char mEditingSelectedLayer; + + + TwoSpriteButtonGL *mObjectLayerViewButton; + + LeftButtonGL *mObjectPrevLayerButton; + RightButtonGL *mObjectNextLayerButton; + + SpriteButtonGL *mObjectBottomLayerButton; + SpriteButtonGL *mObjectTopLayerButton; + + SpriteButtonGL *mLayerUpButton; + SpriteButtonGL *mLayerDownButton; + + ToggleSpriteButtonGL *mLayerGlowButton; + + + SelectableButtonGL *mTransButton; + + + SelectableButtonGL *mGridButton; + + + + SpriteButtonGL *mZoomButton; + SpriteButtonGL *mUnZoomButton; + + + TextFieldGL *mNameField; + + + + char mDragFromObjectPicker; + + char mClickStartedOnLayer; + + + + Image *mNoDropImage; + Image *mCanDropImage; + + + // for usage tracking of objects in current state + uniqueID mCurrentWorkingStateID; + + }; + +#endif diff --git a/gameSource/StateObjectPicker.cpp b/gameSource/StateObjectPicker.cpp new file mode 100644 index 0000000..36b1ddb --- /dev/null +++ b/gameSource/StateObjectPicker.cpp @@ -0,0 +1,80 @@ +#include "StateObjectPicker.h" + +#include "TilePicker.h" + +extern TilePicker *mainTilePicker; + + +// all share one stack +SimpleVector globalObjectStack; + + + +StateObjectPicker::StateObjectPicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalObjectStack ) { + + mainTilePicker->addActionListener( this ); + + + mTransButton = new SelectableButtonGL( + new Sprite( "transSmall.tga", true ), + 1, + mAnchorX + 2 * (mWidth - 12) / 3 + 2, + mAnchorY, + 12, 12 ); + + add( mTransButton ); + + mTransButton->setSelected( true ); + + mTransButton->addActionListener( this ); + + mTransButton->setToolTip( "tip_trans" ); + } + + + + +StateObjectPicker::~StateObjectPicker() { + + } + + + + +Sprite *StateObjectPicker::getButtonGridBackground() { + if( mTransButton->getSelected() ) { + return mainTilePicker->getBackgroundTile().getSprite(); + } + else { + // no trans + return NULL; + } + } + + +void StateObjectPicker::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mainTilePicker ) { + // tile changed, new background sprite in result display + + // generate new result sprites + getFreshResults(); + + refreshSelectedResourceSprite(); + } + else if( inTarget == mTransButton ) { + // toggle + mTransButton->setSelected( ! mTransButton->getSelected() ); + + getFreshResults(); + + refreshSelectedResourceSprite(); + } + else { + ResourcePicker::actionPerformed( inTarget ); + } + } + + + diff --git a/gameSource/StateObjectPicker.h b/gameSource/StateObjectPicker.h new file mode 100644 index 0000000..4997301 --- /dev/null +++ b/gameSource/StateObjectPicker.h @@ -0,0 +1,58 @@ +#ifndef STATE_OBJECT_PICKER_INCLUDED +#define STATE_OBJECT_PICKER_INCLUDED + +#include "StateObject.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +#include "buttons.h" + + + +class StateObjectPicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + StateObjectPicker( double inAnchorX, double inAnchorY ); + + + + virtual ~StateObjectPicker(); + + + // override this so we can listen to tile-picker too + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // override, returning current tile as background to show + // behind transparent sprites + Sprite *getButtonGridBackground(); + + // to toggle sprite trans + SelectableButtonGL *mTransButton; + + }; + + + +#endif + + + diff --git a/gameSource/StateToolSet.cpp b/gameSource/StateToolSet.cpp new file mode 100644 index 0000000..e23d5fa --- /dev/null +++ b/gameSource/StateToolSet.cpp @@ -0,0 +1,53 @@ +#include "StateToolSet.h" + + + +StateToolSet::StateToolSet( double inAnchorX, double inAnchorY ) + : GUIPanelGL( inAnchorX, inAnchorY, 40, 20, + new Color( 0, 0, 0, 0 ) ) { + + mMoveButton = new SelectableButtonGL( + new Sprite( "move.tga", true ), + 1, + inAnchorX, inAnchorY, 20, 20 ); + + mSpeakButton = new SelectableButtonGL( + new Sprite( "speak.tga", true ), + 1, + inAnchorX + 20, inAnchorY, 20, 20 ); + + mMoveButton->setToolTip( "tip_objMove" ); + mSpeakButton->setToolTip( "tip_objSpeak" ); + + + add( mMoveButton ); + add( mSpeakButton ); + + mMoveButton->addActionListener( this ); + mSpeakButton->addActionListener( this ); + + mMoveButton->setSelected( true ); + } + + + +stateTool StateToolSet::getSelected() { + if( mMoveButton->getSelected() ) { + return move; + } + if( mSpeakButton->getSelected() ) { + return speak; + } + + return move; + } + + + +void StateToolSet::actionPerformed( GUIComponent *inTarget ) { + // select hit, turn others off + mMoveButton->setSelected( inTarget == mMoveButton ); + mSpeakButton->setSelected( inTarget == mSpeakButton ); + + fireActionPerformed( this ); + } diff --git a/gameSource/StateToolSet.h b/gameSource/StateToolSet.h new file mode 100644 index 0000000..fba6b97 --- /dev/null +++ b/gameSource/StateToolSet.h @@ -0,0 +1,37 @@ +#ifndef STATE_TOOL_SET_INCLUDED +#define STATE_TOOL_SET_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/ui/event/ActionListener.h" +#include "minorGems/ui/event/ActionListenerList.h" + +#include "buttons.h" + + +enum stateTool { move, speak }; + + +class StateToolSet : public GUIPanelGL, public ActionListener, + public ActionListenerList { + + + public: + + // sets its width/height automatically + StateToolSet( double inAnchorX, double inAnchorY ); + + + stateTool getSelected(); + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + protected: + SelectableButtonGL *mMoveButton; + SelectableButtonGL *mSpeakButton; + + }; + + +#endif diff --git a/gameSource/StringTree.cpp b/gameSource/StringTree.cpp new file mode 100644 index 0000000..8b20b1e --- /dev/null +++ b/gameSource/StringTree.cpp @@ -0,0 +1,691 @@ +#include "StringTree.h" + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/stringUtils.h" + + +#include + +// for platform-independent ptr -> unsigned int casting +#include + + + +typedef struct valueHolder { + void *value; + char mark; + } valueHolder; + + +#define B 2000 + +class ValueHashTable { + public: + + ~ValueHashTable(); + + void insert( valueHolder *inHolder ); + + void remove( valueHolder *inHolder ); + + valueHolder *lookup( void *inValue ); + + protected: + + SimpleVector mBins[B]; + + }; + + + +ValueHashTable::~ValueHashTable() { + for( int b=0; b *v = &( mBins[b] ); + + int numHits = v->size(); + + for( int i=0; igetElement( i ) ); + } + } + } + + + +void ValueHashTable::insert( valueHolder *inHolder ) { + int b = (uintptr_t)( inHolder->value ) % B; + + mBins[b].push_back( inHolder ); + } + + + +void ValueHashTable::remove( valueHolder *inHolder ) { + int b = (uintptr_t)( inHolder->value ) % B; + + mBins[b].deleteElementEqualTo( inHolder ); + } + + + +valueHolder *ValueHashTable::lookup( void *inValue ) { + int b = (uintptr_t)( inValue ) % B; + + SimpleVector *v = &( mBins[b] ); + + int numHits = v->size(); + + for( int i=0; igetElement( i ) ); + + if( holder->value == inValue ) { + return holder; + } + } + + return NULL; + } + + + + + + + +class StringTreeNode { + public: + StringTreeNode( char inChar, StringTreeNode *inParent ); + + ~StringTreeNode(); + + + void insert( const char *inString, void *inValue, + ValueHashTable *inHashTable ); + + + // removes value at this node, and recursively deletes upward if + // this node is now empty + void remove( valueHolder *inHolder ); + + + StringTreeNode *search( const char *inString ); + + + // is this node empty + char isEmpty(); + + + // use inDownOnly for root node of a match (to get everything below + // that node, but not to left or right of that node) + int countValuesBelow( char inDownOnly ); + + + + // pointers for inNumToGet and inNumToSkip because they are + // used for tracking progress in recursion (modified during call) + // returns num gotten + void getValuesBelow( int *inNumToGet, int *inNumToSkip, + SimpleVector *outValues, + char inDownOnly ); + + + void unmarkValuesBelow( char inDownOnly ); + + + void print(); + + char mChar; + SimpleVector mValues; + + StringTreeNode *mParent; + + StringTreeNode *mLeft; + StringTreeNode *mDown; + StringTreeNode *mRight; + + protected: + + void removeChild( StringTreeNode *inChild ); + + + }; + + + +StringTreeNode::StringTreeNode( char inChar, StringTreeNode *inParent ) + : mChar( inChar ), + mParent( inParent ), mLeft( NULL ), mDown( NULL), mRight( NULL ) { + } + + + +StringTreeNode::~StringTreeNode() { + if( mLeft != NULL ) { + delete mLeft; + } + if( mDown != NULL ) { + delete mDown; + } + if( mRight != NULL ) { + delete mRight; + } + } + + + +extern void printResourceRecord( void *inR ); + + +void StringTreeNode::insert( const char *inString, void *inValue, + ValueHashTable *inHashTable ) { + + if( inString[0] == mChar ) { + // match + + if( inString[1] == '\0' ) { + + // ends here + valueHolder *v = inHashTable->lookup( inValue ); + + if( v == NULL ) { + v = new valueHolder; + v->value = inValue; + v->mark = false; + inHashTable->insert( v ); + } + + mValues.push_back( v ); + } + else { + // pass down, skipping matched char + + if( mDown == NULL ) { + // create it + mDown = new StringTreeNode( inString[1], this ); + } + + mDown->insert( &( inString[1] ), inValue, inHashTable ); + } + } + else { + // non-match + + if( inString[0] < mChar ) { + // left + if( mLeft == NULL ) { + mLeft = new StringTreeNode( inString[0], this ); + } + + mLeft->insert( inString, inValue, inHashTable ); + } + else if( inString[0] > mChar ) { + // right + if( mRight == NULL ) { + mRight = new StringTreeNode( inString[0], this ); + } + + mRight->insert( inString, inValue, inHashTable ); + } + } + } + + + +char StringTreeNode::isEmpty() { + if( mValues.size() == 0 + && mLeft == NULL + && mDown == NULL + && mRight == NULL ) { + + return true; + } + return false; + } + + + + +void StringTreeNode::remove( valueHolder *inHolder ) { + + mValues.deleteElementEqualTo( inHolder ); + + + if( isEmpty() ) { + + if( mParent != NULL ) { + mParent->removeChild( this ); + } + } + } + + + +void StringTreeNode::removeChild( StringTreeNode *inChild ) { + + if( mLeft == inChild ) { + delete mLeft; + mLeft = NULL; + } + if( mDown == inChild ) { + delete mDown; + mDown = NULL; + } + if( mRight == inChild ) { + delete mRight; + mRight = NULL; + } + + + if( isEmpty() ) { + + if( mParent != NULL ) { + mParent->removeChild( this ); + } + } + } + + + + + + + + +StringTreeNode *StringTreeNode::search( const char *inString ) { + if( inString[0] == mChar ) { + // match + + if( inString[1] == '\0' ) { + // ends here + return this; + } + else { + // pass down, skipping matched char + + if( mDown == NULL ) { + // not found + return NULL; + } + return mDown->search( &( inString[1] ) ); + } + } + else { + // non-match + + if( inString[0] < mChar ) { + // left + if( mLeft == NULL ) { + return NULL; + } + + return mLeft->search( inString ); + } + else { + // right + if( mRight == NULL ) { + return NULL; + } + + return mRight->search( inString ); + } + } + } + + + +int StringTreeNode::countValuesBelow( char inDownOnly ) { + + int num = 0; + + if( !inDownOnly && mLeft != NULL ) { + num += mLeft->countValuesBelow( false ); + } + + int numHere = mValues.size(); + + for( int i=0; imark ) { + // mark it + v->mark = true; + + // count it + num++; + } + // else already counted... skip it + } + + + if( mDown != NULL ) { + num += mDown->countValuesBelow( false ); + } + + if( !inDownOnly && mRight != NULL ) { + num += mRight->countValuesBelow( false ); + } + + return num; + } + + + + +void StringTreeNode::getValuesBelow( int *inNumToGet, int *inNumToSkip, + SimpleVector *outValues, + char inDownOnly ) { + + if( !inDownOnly && mLeft != NULL ) { + mLeft->getValuesBelow( inNumToGet, inNumToSkip, + outValues, false ); + } + + if( *inNumToGet == 0 ) { + return; + } + + // self + int numHere = mValues.size(); + + // newest values first + for( int i=numHere-1; i>=0 && *inNumToGet > 0; i-- ) { + + valueHolder *v = *( mValues.getElement( i ) ); + + if( !v->mark ) { + // mark it + v->mark = true; + + if( *inNumToSkip > 0 ) { + (*inNumToSkip) --; + } + else { + // done skipping + + // take value + outValues->push_back( v->value ); + + (*inNumToGet) --; + } + } + } + + + + + if( *inNumToGet == 0 ) { + return; + } + + + if( mDown != NULL ) { + mDown->getValuesBelow( inNumToGet, inNumToSkip, + outValues, false ); + } + + + if( *inNumToGet == 0 ) { + return; + } + + + if( !inDownOnly && mRight != NULL ) { + mRight->getValuesBelow( inNumToGet, inNumToSkip, + outValues, false ); + } + + } + + + +void StringTreeNode::unmarkValuesBelow( char inDownOnly ) { + + if( !inDownOnly && mLeft != NULL ) { + mLeft->unmarkValuesBelow( false ); + } + + int numHere = mValues.size(); + + for( int i=0; imark = false; + } + + + if( mDown != NULL ) { + mDown->unmarkValuesBelow( false ); + } + + if( !inDownOnly && mRight != NULL ) { + mRight->unmarkValuesBelow( false ); + } + } + + + +void StringTreeNode::print() { + + // Graphviz format + + char *valueString = stringDuplicate( "" ); + + /* + for( int i=0; ivalue ); + + delete [] valueString; + valueString = newString; + } + */ + + printf( "n%p; n%p [label = \"%c (%s) {%p}\"]; ", + (void *)this, (void *)this, mChar, valueString, + (void *)this ); + + delete [] valueString; + + if( mLeft != NULL ) { + mLeft->print(); + printf( "n%p -> n%p [label = \"L\", weight=1]; ", + (void *)this, (void *)mLeft ); + } + if( mDown != NULL ) { + mDown->print(); + printf( "n%p -> n%p [label = \"D\", weight=4]; ", + (void *)this, (void *)mDown ); + } + if( mRight != NULL ) { + mRight->print(); + printf( "n%p -> n%p [label = \"R\", weight=1]; ", + (void *)this, (void *)mRight ); + } + + + } + + + + + + + + + +StringTree::StringTree() + : mTreeRoot( NULL ), + mHashTable( new ValueHashTable ) { + } + + +StringTree::~StringTree() { + if( mTreeRoot != NULL ) { + delete mTreeRoot; + } + delete mHashTable; + } + + + +void StringTree::insert( const char *inString, void *inValue ) { + if( mTreeRoot == NULL ) { + mTreeRoot = new StringTreeNode( inString[0], NULL ); + } + + + // insert all suffixes + int numChars = strlen( inString ); + + + for( int i=0; iinsert( &( inString[i] ), inValue, mHashTable ); + } + + /* + printf( "After inseriting %s, tree is:\n", inString ); + mTreeRoot->print(); + + printf( "\n\n\n" ); + */ + } + + +void StringTree::remove( const char *inString, void *inValue ) { + if( mTreeRoot == NULL ) { + return; + } + + valueHolder *holder = mHashTable->lookup( inValue ); + + if( holder != NULL ) { + + + // search for all suffixes to find nodes that hold our value + int numChars = strlen( inString ); + + for( int i=0; isearch( &( inString[i] ) ); + + if( matchNode != NULL ) { + + matchNode->remove( holder ); + } + } + mHashTable->remove( holder ); + + delete holder; + } + + if( mTreeRoot->isEmpty() ) { + delete mTreeRoot; + mTreeRoot = NULL; + } + + /* + printf( "After removing %s, tree is:\n", inString ); + if( mTreeRoot != NULL ) { + mTreeRoot->print(); + } + else { + printf( "NULL" ); + } + printf( "\n\n\n" ); + */ + } + + +int StringTree::countMatches( const char *inSearch ) { + if( mTreeRoot == NULL ) { + return 0; + } + + + StringTreeNode *matchNode; + char downOnly; + + if( inSearch[0] == '\0' ) { + // empty search, whole tree + matchNode = mTreeRoot; + downOnly = false; + } + else { + matchNode = mTreeRoot->search( inSearch ); + downOnly = true; + } + + + if( matchNode != NULL ) { + int numMatches = matchNode->countValuesBelow( downOnly ); + + matchNode->unmarkValuesBelow( downOnly ); + + return numMatches; + } + else { + return 0; + } + } + + + +int StringTree::getMatches( const char *inSearch, + int inNumToSkip, int inNumToGet, + void **outValues ) { + + if( mTreeRoot == NULL ) { + return 0; + } + + + StringTreeNode *matchNode; + char downOnly; + + if( inSearch[0] == '\0' ) { + // empty search, whole tree + matchNode = mTreeRoot; + downOnly = false; + } + else { + matchNode = mTreeRoot->search( inSearch ); + downOnly = true; + } + + if( matchNode == NULL ) { + return 0; + } + + + SimpleVector results; + + // these are modified by call + int numToGet = inNumToGet; + int numToSkip = inNumToSkip; + + matchNode->getValuesBelow( &numToGet, &numToSkip, + &results, downOnly ); + + matchNode->unmarkValuesBelow( downOnly ); + + + int numResults = results.size(); + + + void **resultArray = results.getElementArray(); + + int numToCopy = numResults; + if( inNumToGet < numToCopy ) { + numToCopy = inNumToGet; + } + + + memcpy( outValues, resultArray, numToCopy * sizeof( void * ) ); + + delete [] resultArray; + + + return numToCopy; + } + + diff --git a/gameSource/StringTree.h b/gameSource/StringTree.h new file mode 100644 index 0000000..a0d7ea4 --- /dev/null +++ b/gameSource/StringTree.h @@ -0,0 +1,36 @@ + + +class StringTreeNode; +class ValueHashTable; + + + +// a searchable tree that indexes pointers by strings +class StringTree { + + public: + + StringTree(); + + ~StringTree(); + + + void insert( const char *inString, void *inValue ); + + // supply a string to avoid searching the whole tree for inValue + void remove( const char *inString, void *inValue ); + + + int countMatches( const char *inSearch ); + + // outValues must have space allocated by caller for inNumToGet + // pointers + int getMatches( const char *inSearch, int inNumToSkip, int inNumToGet, + void **outValues ); + + protected: + + StringTreeNode *mTreeRoot; + + ValueHashTable *mHashTable; + }; diff --git a/gameSource/Tile.cpp b/gameSource/Tile.cpp new file mode 100644 index 0000000..14096fa --- /dev/null +++ b/gameSource/Tile.cpp @@ -0,0 +1,300 @@ +#include "Tile.h" +#include "resourceManager.h" +#include "Room.h" + +#include "imageCache.h" +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + + +void Tile::setupDefault() { + memset( (void *)mPixelColors, 0, mPixelDataLength ); + + /* + mPixelColors[0][0].comp.r = 255; + mPixelColors[0][0].comp.a = 255; + + mPixelColors[0][1].comp.g = 255; + mPixelColors[0][1].comp.a = 255; + */ + + const char *defaultName = "default"; + memcpy( mSetName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +Tile::Tile() { + setupDefault(); + } + + + +char Tile::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mPixelDataLength ) { + + memcpy( (void *)mPixelColors, inData, mPixelDataLength ); + + inLength -= mPixelDataLength; + + // remainder is name + if( inLength <= 11 ) { + memcpy( mSetName, &inData[mPixelDataLength], inLength ); + return true; + } + } + return false; + } + + + +Tile::Tile( uniqueID inID, unsigned char *inData, int inLength ) + : mID( inID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + + +Tile::Tile( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "tile", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + + if( ! initFromData( data, length ) ) { + // failure + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // tile failed to load + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + } + + + +void Tile::editTile( int inX, int inY, rgbaColor inNewColor ) { + mPixelColors[inY][inX] = inNewColor; + } + + +rgbaColor Tile::getColor( int inX, int inY ) { + return mPixelColors[inY][inX]; + } + + + +void Tile::editTileSetName( const char *inName ) { + int newLength = strlen( inName ); + if( newLength > 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: Tile set name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mSetName, inName, newLength + 1 ); + } + } + + +#include "minorGems/util/stringUtils.h" + + +char *Tile::getTileSetName() { + return stringDuplicate( mSetName ); + } + + + +#include "minorGems/util/SimpleVector.h" + +void Tile::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + if( ! equal( oldID, mID ) || + ! resourceExists( "tile", mID ) ) { + + // change + + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + saveResourceData( "tile", mID, + mSetName, + bytes, numBytes ); + + delete [] bytes; + } + } + + + +unsigned char *Tile::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( (unsigned char *)mPixelColors, + mPixelDataLength ); + + dataAccum.push_back( (unsigned char *)mSetName, + strlen( mSetName ) + 1 ); + + *outLength = dataAccum.size(); + return dataAccum.getElementArray(); + } + + + +void Tile::saveToPack() { + int numBytes; + unsigned char *bytes = makeBytes( &numBytes ); + + addToPack( "tile", mID, + mSetName, + bytes, numBytes ); + + delete [] bytes; + } + + + + +uniqueID Tile::getUniqueID() { + return mID; + } + + + +const char *Tile::getResourceType() { + return "tile"; + } + +Tile Tile::getDefaultResource() { + return *( Room::sBlankTile ); + } + + + + +void Tile::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, + (unsigned char*)mPixelColors, mPixelDataLength ); + + p = addToUniqueID( p, (unsigned char*)mSetName, strlen( mSetName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *Tile::getImage() { + Image *image = new Image( P, P, 3, false ); + + double *channels[3]; + + int i; + + for( i=0; i<3; i++ ) { + channels[i] = image->getChannel( i ); + } + + for( int y=0; y +void SizeLimitedVector::deleteElementOfType( + Tile inElement ) { + // no delete necessary + } + + + +TileEditor::TileEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_tile" ); + /* + LabelGL *titleLabel = new LabelGL( 0.75, 0.5, 0.25, + 0.1, "Tile Editor", largeText ); + + + + mSidePanel->add( titleLabel ); + */ + + mSidePanel->add( mainColorStack ); + + mainColorStack->addActionListener( this ); + + + mSidePanel->add( mainTilePicker ); + + mainTilePicker->addActionListener( this ); + + + mEditColorButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 7, + 8, + 8 ); + + mSidePanel->add( mEditColorButton ); + + mEditColorButton->addActionListener( this ); + mEditColorButton->setToolTip( "tip_edit_color" ); + + + mEditPaletteButton = + new EditButtonGL( + mainColorStack->getAnchorX() - 9, + mainColorStack->getAnchorY() + mainColorStack->getHeight() - 51, + 8, + 8 ); + + mSidePanel->add( mEditPaletteButton ); + + mEditPaletteButton->addActionListener( this ); + mEditPaletteButton->setToolTip( "tip_edit_palette" ); + + + + /* + // 1-pixel wide white border + // inner panel to provide a 1-pixel wide black gap + + BorderPanel *workingColorBorderPanel = + new BorderPanel( 256, 206, 48, 14, + new Color( 0, 0, 0, 1 ), + new Color( 1, 1, 1, 1 ), + 1 ); + + + // smaller to leave black gap + GUIPanelGL *workingColorPanel = new GUIPanelGL( 258, 208, 44, 10, + mWorkingColor ); + + mSidePanel->add( workingColorBorderPanel ); + workingColorBorderPanel->add( workingColorPanel ); + + + mAddButton = new AddButtonGL( 272, 184, 16, 16 ); + mSidePanel->add( mAddButton ); + mAddButton->addActionListener( this ); + + */ + + + double offset = P; + + double buttonSize = (gameHeight - 2 * offset - 8) / P; + + + rgbaColor c = { { 0,0,0,255 } }; + + + + for( int y=0; yadd( mButtonGrid[y][x] ); + + mButtonGrid[y][x]->addActionListener( this ); + } + } + + + + + + mToolSet = new DrawToolSet( 2, 42 ); + mMainPanel->add( mToolSet ); + mToolSet->addActionListener( this ); + + + + mSelectionButton = new SelectableButtonGL( + new Sprite( "selection.tga", true ), + 1, + mToolSet->getAnchorX() + mToolSet->getWidth() + 5, 42, + 20, 20 ); + + mMainPanel->add( mSelectionButton ); + + mSelectionButton->setSelected( false ); + + mSelectionButton->addActionListener( this ); + + mSelectionButton->setToolTip( "tip_selection" ); + + + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "tileSetName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mSetNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mSetNameField ); + + mSetNameField->setFocus( true ); + //mSetNameField->lockFocus( true ); + + mSetNameField->setCursorPosition( strlen( defaultText ) ); + + mSetNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + P * buttonSize; + + double extra = gameHeight - gridEdge; + + + // center it vertically on tile picker + double addY = mainTilePicker->getAnchorY() + + mainTilePicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addTile" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + + mTransformToolSet = new TransformToolSet( sideButtonsX, + mMiniViewButton->getAnchorY() + - 10 - 100, + true ) ; + + mMainPanel->add( mTransformToolSet ); + + mTransformToolSet->addActionListener( this ); + + + + + double undoButtonY = gameWidth - ( 48 + P * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + + + setTileToEdit( mainTilePicker->getSelectedResource() ); + + + mPenDown = false; + + + // fully-opaque gray to avoid color distortions + Color gridColor( 0.25, 0.25, 0.25, 1.0 ); + + GridOverlay *overlay = new GridOverlay( + 8, + gameWidth - ( 48 + P * buttonSize ), + P * buttonSize, P * buttonSize, + P, + gridColor ); + + mMainPanel->add( overlay ); + + postConstruction(); + } + + + +TileEditor::~TileEditor() { + mSidePanel->remove( mainColorStack ); + mSidePanel->remove( mainTilePicker ); + + } + + + +void TileEditor::setTileToEdit( Tile inTile ) { + mTileToEdit = inTile; + + for( int y=0; ysetColor( mTileToEdit.getColor( x, y ) ); + } + } + refreshMiniView(); + + char *name = mTileToEdit.getTileSetName(); + + mSetNameField->setText( name ); + mSetNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + } + + + +void TileEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mTileToEdit.getSprite( false, false ) ); + } + + + +char TileEditor::recursiveFill( int inX, int inY, rgbaColor inOldColor, + rgbaColor inNewColor, + drawTool inTool ) { + + if( equal( mTileToEdit.getColor( inX, inY ), inOldColor ) + && + !equal( mTileToEdit.getColor( inX, inY ), inNewColor ) ) { + + mButtonGrid[inY][inX]->setColor( inNewColor ); + mTileToEdit.editTile( inX, inY, inNewColor ); + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveFill( inX - 1, inY, inOldColor, inNewColor, inTool ); + } + if( inX < P - 1 ) { + recursiveFill( inX + 1, inY, inOldColor, inNewColor, inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveFill( inX, inY - 1, inOldColor, inNewColor, inTool ); + } + if( inY < P - 1 ) { + recursiveFill( inX, inY + 1, inOldColor, inNewColor, inTool ); + } + } + + return true; + } + + return false; + } + + + +char TileEditor::recursiveSelectionFill( int inX, int inY, + rgbaColor inOldColor, + char inOldSelection, + char inNewSelection, + drawTool inTool ) { + + if( equal( mTileToEdit.getColor( inX, inY ), inOldColor ) + && + SelectionManager::isInSelection( inX, inY ) == inOldSelection + && + SelectionManager::isInSelection( inX, inY ) != inNewSelection ) { + + SelectionManager::toggleSelection( inX, inY, inNewSelection ); + + mButtonGrid[inY][inX]->setSelection( inNewSelection ); + + if( inNewSelection ) { + SelectionManager::setColor( + inX, inY, + mTileToEdit.getColor( inX, inY ) ); + SelectionManager::setTrans( + inX, inY, + false ); + } + + // call on neighbors + if( inTool == fill || inTool == horLine ) { + if( inX > 0 ) { + recursiveSelectionFill( inX - 1, inY, inOldColor, + inOldSelection, inNewSelection, + inTool ); + } + if( inX < P - 1 ) { + recursiveSelectionFill( inX + 1, inY, inOldColor, + inOldSelection, inNewSelection, + inTool ); + } + } + + if( inTool == fill || inTool == verLine ) { + if( inY > 0 ) { + recursiveSelectionFill( inX, inY - 1, inOldColor, + inOldSelection, inNewSelection, + inTool ); + } + if( inY < P - 1 ) { + recursiveSelectionFill( inX, inY + 1, inOldColor, + inOldSelection, inNewSelection, + inTool ); + } + } + + return true; + } + + return false; + } + + + +void TileEditor::toggleSelection() { + mSelectionButton->setSelected( ! mSelectionButton->getSelected() ); + + char showSel = mSelectionButton->getSelected(); + + for( int y=0; ysetSelection( + SelectionManager::isInSelection( x, y ) && showSel ); + } + } + + // commit selection only when it is turned on + // (right before it is turned off, the last edits of selection have updated + // the selected colors) + if( showSel ) { + // copy colors + for( int y=0; y + setOverlay( false ); + } + } + } + + + +void TileEditor::colorEditorClosed() { + // it might have been closed by an EditPalette button press + if( mainColorEditor->mEditPalettePressed ) { + showPaletteEditor(); + } + } + + + + +void TileEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + if( inTarget == mainColorStack ) { + // new color picked on stack + } + else if( inTarget == mainTilePicker ) { + if( ! mAddAction && + ! mainTilePicker->wasLastActionFromPress() ) { + // will change tile + + mUndoStack.push_back( mTileToEdit ); + mUndoButton->setEnabled( true ); + + setTileToEdit( mainTilePicker->getSelectedResource() ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mSetNameField ) { + + mUndoStack.push_back( mTileToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mTileToEdit.editTileSetName( mSetNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addTile(); + } + else if( inTarget == mEditColorButton ) { + mainColorEditor->setEditPaletteButtonVisible( true ); + showColorEditor(); + } + else if( inTarget == mEditPaletteButton ) { + showPaletteEditor(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + Tile last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mTileToEdit ); + mRedoButton->setEnabled( true ); + + setTileToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + Tile next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mTileToEdit ); + mUndoButton->setEnabled( true ); + + setTileToEdit( next ); + } + else if( inTarget == mSelectionButton ) { + toggleSelection(); + } + else if( inTarget == mTransformToolSet ) { + mUndoStack.push_back( mTileToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + Tile oldTileState = mTileToEdit; + + switch( mTransformToolSet->getLastPressed() ) { + case flipH: { + for( int y=0; ysetColor( flipColor ); + mTileToEdit.editTile( x, y, flipColor ); + } + } + } + break; + case flipV: { + for( int y=0; ysetColor( flipColor ); + mTileToEdit.editTile( x, y, flipColor ); + } + } + + } + break; + case rotateCCW: { + for( int y=0; ysetColor( flipColor ); + mTileToEdit.editTile( x, y, flipColor ); + } + } + + } + break; + case rotateCW: { + for( int y=0; ysetColor( flipColor ); + mTileToEdit.editTile( x, y, flipColor ); + } + } + + } + break; + case clear: { + rgbaColor black; + black.comp.r = 0; + black.comp.g = 0; + black.comp.b = 0; + black.comp.a = 255; + + for( int y=0; ysetColor( black ); + mTileToEdit.editTile( x, y, black ); + } + } + } + break; + case colorize: { + rgbaColor c = + mainColorStack->getSelectedColor(); + + for( int y=0; ysetColor( oldColor ); + mTileToEdit.editTile( x, y, oldColor ); + } + } + } + break; + } + + refreshMiniView(); + } + else if( inTarget == mToolSet ) { + clearStampOverlay(); + } + else if( !mainColorEditor->getDragging() ){ + // check grid + // but only if not in the middle of drag-picking a color in the editor + + char found = false; + + for( int y=0; y

    wasLastActionHover() ) { + + found = true; + + Tile oldTileState = mTileToEdit; + char changed = false; + + switch( mToolSet->getSelected() ) { + case pen: { + if( mSelectionButton->getSelected() ) { + // edit selection with pen + + if( !mPenDown ) { + // set ink until released + mSelectionInk = + ! SelectionManager:: + isInSelection( x, y ); + + mPenDown = true; + changed = true; + } + + // use ink already set + SelectionManager::toggleSelection( + x, y, mSelectionInk ); + + mButtonGrid[y][x]->setSelection( + mSelectionInk ); + + if( mSelectionInk ) { + SelectionManager::setColor( + x, y, + mTileToEdit.getColor( x, y ) ); + SelectionManager::setTrans( + x, y, false ); + } + } + else { + if( mainColorEditor->isVisible() ) { + // push edited color onto stack first + mainColorEditor->addColor(); + } + + rgbaColor c = + mainColorStack->getSelectedColor(); + + if( ! equal( c, + mTileToEdit.getColor( x, y ) ) ) { + + mButtonGrid[y][x]->setColor( c ); + mTileToEdit.editTile( x, y, c ); + refreshMiniView(); + + // don't count single pen dots as undoable + // until pen is released + // save undo state only on on initial + // presses + if( !mPenDown ) { + mPenDown = true; + changed = true; + } + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + } + break; + case horLine: + case verLine: + case fill: + if( mSelectionButton->getSelected() ) { + if( !mPenDown ) { + mSelectionInk = ! SelectionManager:: + isInSelection( x, y ); + } + + recursiveSelectionFill( + x, y, + mTileToEdit.getColor( x, y ), + SelectionManager:: + isInSelection( x, y ), + mSelectionInk, + mToolSet->getSelected() ); + + if( !mPenDown ) { + mPenDown = true; + } + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + else { + + if( mainColorEditor->isVisible() ) { + // push edited color onto stack first + mainColorEditor->addColor(); + } + + changed = recursiveFill( + x, y, + mTileToEdit.getColor( x, y ), + mainColorStack->getSelectedColor(), + mToolSet->getSelected() ); + refreshMiniView(); + } + break; + case pickColor: + mainColorStack->pushColor( + mTileToEdit.getColor( x, y ) ); + break; + case stamp: { + if( !mPenDown ) { + changed = true; + mPenDown = true; + } + // insert colors from selection + + intPair center = + SelectionManager::getSelectionCenter( true ); + + for( int sy=0; sy= 0 && ix >= 0 ) { + rgbaColor c = SelectionManager:: + getColor( sx, sy ); + + mButtonGrid[iy][ix]->setColor( c ); + mTileToEdit.editTile( + ix, iy, c ); + + } + } + } + } + + refreshMiniView(); + + if( !mButtonGrid[y][x]->isPressed() ) { + // a release + mPenDown = false; + } + } + break; + } + + + + if( changed ) { + mUndoStack.push_back( oldTileState ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + + + } + else if( inTarget == mButtonGrid[y][x] && + mButtonGrid[y][x]->wasLastActionHover() ) { + found = true; + + if( mToolSet->getSelected() == stamp ) { + + // preview of selection that will be inserted + // upon click + + // first, turn all overlays off + for( int by=0; by + setOverlay( false ); + } + } + + intPair center = + SelectionManager::getSelectionCenter( true ); + + for( int sy=0; sy= 0 && ix >= 0 ) { + rgbaColor c = SelectionManager:: + getColor( sx, sy ); + + mButtonGrid[iy][ix]-> + setOverlayColor( c ); + mButtonGrid[iy][ix]-> + setOverlay( true ); + } + } + } + } + + } + } + } + } + } + + + } + + + +void TileEditor::editorClosing() { + addTile(); + } + + + +void TileEditor::addTile() { + mAddAction = true; + mTileToEdit.finishEdit(); + mainTilePicker->setSelectedResource( mTileToEdit, true ); + mAddAction = false; + } + diff --git a/gameSource/TileEditor.h b/gameSource/TileEditor.h new file mode 100644 index 0000000..8759b67 --- /dev/null +++ b/gameSource/TileEditor.h @@ -0,0 +1,105 @@ +#ifndef TILE_EDITOR_INCLUDED +#define TILE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "Tile.h" +#include "DrawToolSet.h" +#include "TransformToolSet.h" +#include "SizeLimitedVector.h" + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +class TileEditor : public Editor { + + public: + + TileEditor( ScreenGL *inScreen ); + + ~TileEditor(); + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addTile(); + + + // override from Editor + virtual void colorEditorClosed(); + + + + AddButtonGL *mAddButton; + char mAddAction; + + void setTileToEdit( Tile inTile ); + + void refreshMiniView(); + + + HighlightColorButtonGL *mButtonGrid[P][P]; + + + SpriteButtonGL *mMiniViewButton; + + Tile mTileToEdit; + + + EditButtonGL *mEditColorButton; + EditButtonGL *mEditPaletteButton; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + TransformToolSet *mTransformToolSet; + + + SelectableButtonGL *mSelectionButton; + + + + DrawToolSet *mToolSet; + + // returns true if anything changed + char recursiveFill( int inX, int inY, rgbaColor inOldColor, + rgbaColor inNewColor, + drawTool inTool ); + + // version for selection + char recursiveSelectionFill( int inX, int inY, + rgbaColor inOldColor, + char inOldSelection, + char inNewSelection, + drawTool inTool ); + + + void toggleSelection(); + + void clearStampOverlay(); + + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mSetNameField; + + + char mPenDown; + char mSelectionInk; + + }; + +#endif diff --git a/gameSource/TilePicker.cpp b/gameSource/TilePicker.cpp new file mode 100644 index 0000000..ba440de --- /dev/null +++ b/gameSource/TilePicker.cpp @@ -0,0 +1,36 @@ +#include "TilePicker.h" + +// all share one stack +SimpleVector globalTileStack; + + +TilePicker::TilePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, &globalTileStack ) { + } + + + + +TilePicker::~TilePicker() { + + } + + +void TilePicker::setBackgroundTile( Tile inTile ) { + + if( !equal( inTile.getUniqueID(), + mChosenBackgroundTile.getUniqueID () ) ) { + + mChosenBackgroundTile = inTile; + + fireActionPerformed( this ); + } + + } + + +Tile TilePicker::getBackgroundTile() { + return mChosenBackgroundTile; + } + diff --git a/gameSource/TilePicker.h b/gameSource/TilePicker.h new file mode 100644 index 0000000..de59fbf --- /dev/null +++ b/gameSource/TilePicker.h @@ -0,0 +1,58 @@ +#ifndef TILE_PICKER_INCLUDED +#define TILE_PICKER_INCLUDED + +#include "Tile.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class TilePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + TilePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~TilePicker(); + + + + // used for background in other pickers + // does not change when selected tile in picker changes + + + // fires an event to listeners + void setBackgroundTile( Tile inTile ); + + Tile getBackgroundTile(); + + + + protected: + + + Tile mChosenBackgroundTile; + + }; + + + +#endif + + + diff --git a/gameSource/Timbre.cpp b/gameSource/Timbre.cpp new file mode 100644 index 0000000..95aa400 --- /dev/null +++ b/gameSource/Timbre.cpp @@ -0,0 +1,234 @@ +#include "Timbre.h" + +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/log/AppLog.h" + +#include +#include + + +double twelthRootOfTwo = pow( 2, 1.0/12 ); + +// #define SCALE_SIZE 7 + +// for major scale +// W, W, H, W, W, W, H +//int halfstepMap[ SCALE_SIZE ] = { 0, 2, 4, 5, 7, 9, 11 }; + +// minor scale +// W,H,W,W,H,W,W +//int halfstepMap[ SCALE_SIZE ] = { 0, 2, 3, 5, 7, 8, 10 }; + +#define MAX_SCALE_SIZE 12 + +int halfstepMap[ MAX_SCALE_SIZE ] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + +int usedScaleNotes = 5; + + +void setDefaultScale() { + usedScaleNotes = 5; + // major pentatonic + //int halfstepMap[ SCALE_SIZE ] = { 0, 2, 4, 7, 9 }; + + // minor pentatonic + //int halfstepMap[ SCALE_SIZE ] = { 0, 3, 5, 7, 10 }; + halfstepMap[0] = 0; + halfstepMap[1] = 3; + halfstepMap[2] = 5; + halfstepMap[3] = 7; + halfstepMap[4] = 10; + } + + +void setTimbreScale( char inToneOn[12] ) { + usedScaleNotes = 0; + + int nextStep = 0; + for( int i=0; i<12; i++ ) { + if( inToneOn[i] ) { + halfstepMap[nextStep] = i; + usedScaleNotes ++; + nextStep++; + } + } + printf( "halfstep map= " ); + for( int i=0; i> 8 & 0xFF; + unsigned char lsb = val && 0xFF; + + fwrite( &msb, 1, 1, aiffFile ); + fwrite( &lsb, 1, 1, aiffFile ); + } + fclose( aiffFile ); + + } +*/ + + + + +Timbre::Timbre( int inSampleRate, + double inLoudness, + double inBaseFrequency, + int inNumWaveTableEntries, + double( *inWaveFunction )( double ), + int inPeriodsPerTable ) + : mNumWaveTableEntries( inNumWaveTableEntries ), + mWaveTable( new Sint16*[ inNumWaveTableEntries ] ), + mWaveTableLengths( new int[ inNumWaveTableEntries ] ), + mActiveNoteCount( 0 ) { + + // build wave table for each possible pitch in image + + for( int i=0; i inSampleRate / 2 ) { + // cap at Nyquist limit + freq = inSampleRate / 2; + } + + + + double period = 1.0 / freq; + + + int tableLength = (int)( + rint( inPeriodsPerTable * period * inSampleRate ) ); + + if( tableLength == 0 ) { + AppLog::getLog()->logPrintf( Log::CRITICAL_ERROR_LEVEL, + "table length 0 for freq %f\n", + freq ); + } + + mWaveTableLengths[i] = tableLength; + mWaveTable[i] = new Sint16[ tableLength ]; + + // store double samples in temp table so we can compute + // max value for normalization + double *tempTable = new double[ tableLength ]; + double maxValue = 0; + + int s; + + for( s=0; s maxValue ) { + maxValue = waveValue; + } + else if( -waveValue > maxValue ) { + maxValue = -waveValue; + } + } + //printf( "max value = %f\n", maxValue ); + + // now normalize and convert to int + for( s=0; s + + +// one of these must be called before constructing first timbre +void setDefaultScale(); + +void setTimbreScale( char inToneOn[12] ); + + + +class Timbre { + public: + + /** + * Constructs a timbre and fills its wavetables. + * + * @param inSampleRate number of samples per second. + * @param inLoudness a scale factor in [0,1]. + * @param inBaseFrequency the lowest note in the wave table, in Hz. + * This is also the key for the major scale held in the wave table. + * @param inNumWaveTableEntries the number of wavetable entries. + * @param inPeriodsPerTable number of periods stored in each table. + * For functions that don't have a period of 2PI (like noise), this + * will make a more accurrate signal representation when the + * wave table is looped. Defaults to 1 (fine for 2PI functions). + * @param inWaveFunction a function mapping a double parameter t + * to a wave height in [-1,1]. Must have a period of 2pi. + */ + Timbre( int inSampleRate, + double inLoudness, + double inBaseFrequency, + int inNumWaveTableEntries, + double( *inWaveFunction )( double ), + int inPeriodsPerTable = 1 ); + + ~Timbre(); + + + + int mNumWaveTableEntries; + // mWaveTable[x] corresponds to a wave with frequency of + // getFrequency(x) + Sint16 **mWaveTable; + int *mWaveTableLengths; + + + // how many playing notes are still using this timbre? + int mActiveNoteCount; + + }; diff --git a/gameSource/TimbreEditor.cpp b/gameSource/TimbreEditor.cpp new file mode 100644 index 0000000..1e5087a --- /dev/null +++ b/gameSource/TimbreEditor.cpp @@ -0,0 +1,701 @@ +#include "TimbreEditor.h" +#include "TimbrePicker.h" +#include "BorderPanel.h" +#include "labels.h" +#include "SelectionManager.h" +#include "packSaver.h" +#include "GridOverlay.h" + +#include "minorGems/util/log/AppLog.h" + + +extern TimbrePicker *mainTimbrePicker; + + + +extern int gameWidth, gameHeight; + + +extern TextGL *largeTextFixed; + + + +template <> +void SizeLimitedVector::deleteElementOfType( + TimbreResource inElement ) { + // no delete necessary + } + + + +static rgbaColor emptyC = { { 0,0,0,255 } }; + + + +TimbreEditor::TimbreEditor( ScreenGL *inScreen ) + : Editor( inScreen ), + mPlayerTimbreIndex( 0 ), + mUndoStack( MAX_UNDOS, false ) { + + mCloseButton->setToolTip( "tip_closeEdit_timbre" ); + + mSidePanel->add( mainTimbrePicker ); + + mainTimbrePicker->addActionListener( this ); + + + + double offset = P; + + double buttonSize = (gameHeight - 2 * offset - 8) / P; + + + rgbaColor c = { { 0,0,0,255 } }; + + + + for( int y=0; yadd( mButtonGrid[y][x] ); + + mButtonGrid[y][x]->addActionListener( this ); + } + + mZeroButtons[y] =new ColorButtonGL( + c, + 8 - buttonSize, + gameWidth - ( 48 + (y + 1) * buttonSize ), + buttonSize, + buttonSize ); + + mMainPanel->add( mZeroButtons[y] ); + + mZeroButtons[y]->addActionListener( this ); + + // force dark border to make black button invisible + mZeroButtons[y]->setColor( emptyC, true ); + } + + + + + EightPixelLabel *fieldLabel = new EightPixelLabel( 150, 54, + "timbreName" ); + mMainPanel->add( fieldLabel ); + + + + int fieldHeight = 8; + int fieldWidth = 8 * 10; + + const char *defaultText = "default"; + + mNameField = new TextFieldGL( 150, + 43, + fieldWidth, + fieldHeight, + 1, + defaultText, + largeTextFixed, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + 10, + false ); + mMainPanel->add( mNameField ); + + mNameField->setFocus( true ); + //mNameField->lockFocus( true ); + + mNameField->setCursorPosition( strlen( defaultText ) ); + + mNameField->addActionListener( this ); + + + + + + + // center add button + double gridEdge = 8 + buttonSize * FL; + + double extra = gameHeight - gridEdge; + + + // center it vertically on timbre picker + double addY = mainTimbrePicker->getAnchorY() + + mainTimbrePicker->getHeight() - 15; + + double sideButtonsX = gridEdge + (extra - 16) / 2; + + mAddButton = new AddButtonGL( sideButtonsX, + addY, + 16, 16 ); + mMainPanel->add( mAddButton ); + mAddButton->addActionListener( this ); + mAddButton->setToolTip( "tip_addTimbre" ); + + mAddAction = false; + + + double miniButtonSize = P + 4; + + mMiniViewButton = new SpriteButtonGL( + NULL, 1, + gridEdge + ( extra - miniButtonSize ) / 2, + addY - 24, + miniButtonSize, + miniButtonSize ); + + mMainPanel->add( mMiniViewButton ); + + + + double undoButtonY = gameWidth - ( 48 + FL * buttonSize ); + + mUndoButton = new UndoButtonGL( sideButtonsX, undoButtonY, 16, 16 ); + mMainPanel->add( mUndoButton ); + mUndoButton->addActionListener( this ); + mUndoButton->setEnabled( false ); + + mRedoButton = new RedoButtonGL( sideButtonsX, undoButtonY + 19, 16, 16 ); + mMainPanel->add( mRedoButton ); + mRedoButton->addActionListener( this ); + mRedoButton->setEnabled( false ); + + + Color *thumbColor = new Color( .5, .5, .5, .5 ); + Color *borderColor = new Color( .35, .35, .35, .35 ); + + + mAttackSlider = new ToolTipSliderGL( 8, 48, + 40, 10, + NULL, 0, + new Color( 0, 1, 0, 1 ), + new Color( 1, 1, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mAttackSlider ); + mAttackSlider->setThumbPosition( 1.0 ); + mAttackSlider->addActionListener( this ); + mAttackSlider->setToolTip( "tip_attack" ); + + + mHoldSlider = new ToolTipSliderGL( 50, 48, + 40, 10, + NULL, 0, + new Color( 1, 1, 0, 1 ), + new Color( 1, 1, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mHoldSlider ); + mHoldSlider->setThumbPosition( 1.00 ); + mHoldSlider->addActionListener( this ); + mHoldSlider->setToolTip( "tip_hold" ); + + + + mReleaseSlider = new ToolTipSliderGL( 92, 48, + 40, 10, + NULL, 0, + new Color( 1, 1, 0, 1 ), + new Color( 1, 0, 0, 1 ), + thumbColor->copy(), + borderColor->copy(), + 1, 4, 1 ); + + mMainPanel->add( mReleaseSlider ); + mReleaseSlider->setThumbPosition( 0 ); + mReleaseSlider->addActionListener( this ); + mReleaseSlider->setToolTip( "tip_release" ); + + + delete thumbColor; + delete borderColor; + + + + for( int i=0; igetAnchorX() + 3; + double y = mMiniViewButton->getAnchorY() - 10 - 11 * (i+1) - 1; + + mOctaveButtons[i] = new SelectableButtonGL( + new Sprite( "octave.tga", true ), + 1, + x, y, 12, 12 ); + + mOctaveButtons[i]->setToolTip( "tip_octave" ); + + mMainPanel->add( mOctaveButtons[i] ); + + mOctaveButtons[i]->addActionListener( this ); + mOctaveButtons[i]->setEnabled( true ); + } + + + + + setTimbreToEdit( mainTimbrePicker->getSelectedResource() ); + + + + + + + mPenDown = false; + mIgnoreSliders = false; + + + GridOverlay *overlay = new GridOverlay( + 8, + gameWidth - ( 48 + F * buttonSize ), + FL * buttonSize, F * buttonSize, + F ); + + mMainPanel->add( overlay ); + + + postConstruction(); + } + + + +TimbreEditor::~TimbreEditor() { + mSidePanel->remove( mainTimbrePicker ); + } + + +extern Color harmonicLowColor; +extern Color harmonicHighColor; + +#include "musicPlayer.h" + + +void TimbreEditor::setTimbreToEdit( TimbreResource inTimbre, + char inUpdatePlayerTimbre, + char inUpdatePlayerEnvelope ) { + mTimbreToEdit = inTimbre; + /* + rgbaColor fullC = { { (unsigned char)( harmonicLowColor.r * 255 ), + (unsigned char)( harmonicLowColor.g * 255 ), + (unsigned char)( harmonicLowColor.b * 255 ) } }; + rgbaColor fullHotC = { { (unsigned char)( harmonicHighColor.r * 255 ), + (unsigned char)( harmonicHighColor.g * 255 ), + (unsigned char)( harmonicHighColor.b * 255 ) } }; + */ + + rgbaColor fullHotC = mTimbreToEdit.getTimbreColor(); + rgbaColor fullC = fullHotC; + fullC.comp.r = (unsigned char)( fullC.comp.r * 0.50 ); + fullC.comp.g = (unsigned char)( fullC.comp.g * 0.50 ); + fullC.comp.b = (unsigned char)( fullC.comp.b * 0.50 ); + + + + for( int y=0; y x ) { + + mButtonGrid[F - y - 1][x]->setColor( + blend( fullC, fullHotC, x / (double)FL ), true ); + } + else { + mButtonGrid[F - y - 1][x]->setColor( emptyC ); + } + } + } + + + refreshMiniView(); + + char *name = mTimbreToEdit.getTimbreName(); + + mNameField->setText( name ); + mNameField->setCursorPosition( strlen( name ) ); + + delete [] name; + + + mIgnoreSliders = true; + // round to nearest pixel + // attack slider works in reverse + mAttackSlider->setThumbPosition( + (int)( (255 - mTimbreToEdit.getAttack()) / + 255.0 * 80 ) + / 80.0 ); + + mHoldSlider->setThumbPosition( + (int)( mTimbreToEdit.getHold() / + 255.0 * 80 ) + / 80.0 ); + + mReleaseSlider->setThumbPosition( + (int)( mTimbreToEdit.getRelease() / + 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + + + int octave = mTimbreToEdit.getOctavesDown(); + + for( int i=0; isetSelected( i == octave ); + } + + + + mTimbreToEdit.setInPlayer( mPlayerTimbreIndex, + inUpdatePlayerTimbre, inUpdatePlayerEnvelope ); + } + + + +void TimbreEditor::setPlayerTimbreToEdit( int inTimbre ) { + mPlayerTimbreIndex = inTimbre; + } + + + +void TimbreEditor::refreshMiniView() { + // don't use cached version + mMiniViewButton->setSprite( mTimbreToEdit.getSprite( false, false ) ); + } + + +void TimbreEditor::saveUndoPoint() { + mUndoStack.push_back( mTimbreToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + + +void TimbreEditor::actionPerformed( GUIComponent *inTarget ) { + // superclass + Editor::actionPerformed( inTarget ); + + + for( int i=0; igetSelected(); + + if( !oldSelected ) { + saveUndoPoint(); + + mTimbreToEdit.editOctavesDown( i ); + + setTimbreToEdit( mTimbreToEdit, true, false ); + } + return; + } + } + + + if( inTarget == mainTimbrePicker ) { + TimbreResource timbrePicked = mainTimbrePicker->getSelectedResource(); + + if( ! mAddAction && + ! mainTimbrePicker->wasLastActionFromPress() ) { + // will change timbre + + mUndoStack.push_back( mTimbreToEdit ); + mUndoButton->setEnabled( true ); + + setTimbreToEdit( timbrePicked ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + } + } + else if( inTarget == mNameField ) { + mUndoStack.push_back( mTimbreToEdit ); + mUndoButton->setEnabled( true ); + + // new branch... "redo" future now impossible + mRedoStack.deleteAll(); + mRedoButton->setEnabled( false ); + + mTimbreToEdit.editTimbreName( mNameField->getText() ); + } + else if( inTarget == mAddButton ) { + addTimbre(); + } + else if( inTarget == mUndoButton ) { + int lastIndex = mUndoStack.size() - 1; + + TimbreResource last = *( mUndoStack.getElement( lastIndex ) ); + mUndoStack.deleteElement( lastIndex ); + if( mUndoStack.size() == 0 ) { + mUndoButton->setEnabled( false ); + } + + mRedoStack.push_back( mTimbreToEdit ); + mRedoButton->setEnabled( true ); + + setTimbreToEdit( last ); + } + else if( inTarget == mRedoButton ) { + int nextIndex = mRedoStack.size() - 1; + + TimbreResource next = *( mRedoStack.getElement( nextIndex ) ); + mRedoStack.deleteElement( nextIndex ); + if( mRedoStack.size() == 0 ) { + mRedoButton->setEnabled( false ); + } + + mUndoStack.push_back( mTimbreToEdit ); + mUndoButton->setEnabled( true ); + + setTimbreToEdit( next ); + } + else if( inTarget == mAttackSlider && ! mIgnoreSliders ) { + if( mAttackSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + // attack reversed + int attack = 255 - + (int)( 255 * mAttackSlider->getThumbPosition() ); + int hold = + (int)( 255 * mHoldSlider->getThumbPosition() ); + int release = + (int)( 255 * mReleaseSlider->getThumbPosition() ); + + if( release + attack + hold > 255 ) { + if( hold > 0 ) { + // push hold slider + hold = 255 - attack - release; + + if( hold < 0 ) { + hold = 0; + } + + mIgnoreSliders = true; + // round to nearest pixel + mHoldSlider->setThumbPosition( + (int)( hold / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + + if( release + attack + hold > 255 ) { + // push release + release = 255 - attack - hold; + mIgnoreSliders = true; + // round to nearest pixel + mReleaseSlider->setThumbPosition( + (int)( release / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + } + + + mTimbreToEdit.editEnvelope( attack, hold, release ); + + if( mAttackSlider->mJustReleased ) { + // actually adjust + + mTimbreToEdit.setInPlayer( mPlayerTimbreIndex, + false, true ); + } + } + else if( inTarget == mHoldSlider && ! mIgnoreSliders ) { + if( mHoldSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + // attack reversed + int attack = 255 - + (int)( 255 * mAttackSlider->getThumbPosition() ); + int hold = + (int)( 255 * mHoldSlider->getThumbPosition() ); + int release = + (int)( 255 * mReleaseSlider->getThumbPosition() ); + + if( release + attack + hold > 255 ) { + + if( attack > 0 ) { + // push attack slider + attack = 255 - release - hold; + + if( attack < 0 ) { + attack = 0; + } + + mIgnoreSliders = true; + // round to nearest pixel + mAttackSlider->setThumbPosition( + (int)( (255 - attack) / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + + if( release + attack + hold > 255 ) { + // push release + release = 255 - hold - attack; + mIgnoreSliders = true; + // round to nearest pixel + mReleaseSlider->setThumbPosition( + (int)( release / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + } + + + mTimbreToEdit.editEnvelope( attack, hold, release ); + + if( mHoldSlider->mJustReleased ) { + // actually adjust + + mTimbreToEdit.setInPlayer( mPlayerTimbreIndex, + false, true ); + } + } + else if( inTarget == mReleaseSlider && ! mIgnoreSliders ) { + if( mReleaseSlider->mJustPressed ) { + // first move in this adjustment, save an undo point here + saveUndoPoint(); + } + + // release reversed + int attack = 255 - + (int)( 255 * mAttackSlider->getThumbPosition() ); + int hold = + (int)( 255 * mHoldSlider->getThumbPosition() ); + int release = + (int)( 255 * mReleaseSlider->getThumbPosition() ); + + if( release + attack + hold > 255 ) { + if( hold > 0 ) { + if( hold < 0 ) { + hold = 0; + } + + // push hold + hold = 255 - release - attack; + mIgnoreSliders = true; + // round to nearest pixel + mHoldSlider->setThumbPosition( + (int)( hold / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + + + if( release + attack + hold > 255 ) { + // push attack slider + attack = 255 - release - hold; + + mIgnoreSliders = true; + // round to nearest pixel + mAttackSlider->setThumbPosition( + (int)( (255 - attack) / 255.0 * 80 ) + / 80.0 ); + mIgnoreSliders = false; + } + } + + + mTimbreToEdit.editEnvelope( attack, hold, release ); + + if( mReleaseSlider->mJustReleased ) { + // actually adjust + + mTimbreToEdit.setInPlayer( mPlayerTimbreIndex, + false, true ); + } + } + else { + // check grid + char found = false; + + for( int y=0; ywasLastActionHover() ) + || + ( inTarget == mZeroButtons[y] && + ! mZeroButtons[y]->wasLastActionHover() ) ) { + + if( inTarget == mZeroButtons[y] ) { + // okay, since we're leaving loop anyway + x = -1; + } + + + ColorButtonGL *targetButton = (ColorButtonGL*)inTarget; + + found = true; + + if( !mPenDown ) { + mPenDown = true; + // save undo only from start of mouse action + saveUndoPoint(); + } + else { + if( ! targetButton->isPressed() ) { + // release + mPenDown = false; + } + } + + + int oldHarmonicLevel = + mTimbreToEdit.getHarmonicLevel( F - y - 1 ); + + if( x+1 != oldHarmonicLevel ) { + + + // improve performance by only updating when changed + + + + mTimbreToEdit.editHarmonic( F - y - 1, x+1 ); + + setTimbreToEdit( mTimbreToEdit, true, false ); + } + + } + } + } + } + + + } + + + +void TimbreEditor::editorClosing() { + addTimbre(); + } + + + +void TimbreEditor::addTimbre() { + mAddAction = true; + mTimbreToEdit.finishEdit(); + mainTimbrePicker->setSelectedResource( mTimbreToEdit, true ); + mAddAction = false; + } + diff --git a/gameSource/TimbreEditor.h b/gameSource/TimbreEditor.h new file mode 100644 index 0000000..66bb836 --- /dev/null +++ b/gameSource/TimbreEditor.h @@ -0,0 +1,92 @@ +#ifndef TIMBRE_EDITOR_INCLUDED +#define TIMBRE_EDITOR_INCLUDED + + +#include "Editor.h" +#include "buttons.h" +#include "TimbreResource.h" +#include "SizeLimitedVector.h" +#include "ToolTipSliderGL.h" + + + +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" + + +#define NUM_OCTAVES 5 + +class TimbreEditor : public Editor { + + public: + + TimbreEditor( ScreenGL *inScreen ); + + ~TimbreEditor(); + + + // sets index of timbre to realtime-edit in musicPlayer + void setPlayerTimbreToEdit( int inTimbre ); + + + virtual void actionPerformed( GUIComponent *inTarget ); + + + protected: + + int mPlayerTimbreIndex; + + + // implemented by all subclasses + // called by parent class when editor is being closed + virtual void editorClosing(); + + // triggered by add button or close + void addTimbre(); + + + AddButtonGL *mAddButton; + char mAddAction; + + void setTimbreToEdit( TimbreResource inTimbre, + char inUpdatePlayerTimbre = true, + char inUpdatePlayerEnvelope = true ); + + void refreshMiniView(); + + void saveUndoPoint(); + + + ColorButtonGL *mButtonGrid[F][FL]; + // invisible buttons off left edge, to field zero clicks + ColorButtonGL *mZeroButtons[F]; + + + SpriteButtonGL *mMiniViewButton; + + TimbreResource mTimbreToEdit; + + + UndoButtonGL *mUndoButton; + RedoButtonGL *mRedoButton; + + + SizeLimitedVector mUndoStack; + SimpleVector mRedoStack; + + + TextFieldGL *mNameField; + + ToolTipSliderGL *mAttackSlider; + ToolTipSliderGL *mHoldSlider; + ToolTipSliderGL *mReleaseSlider; + + + SelectableButtonGL *mOctaveButtons[ NUM_OCTAVES ]; + + + char mPenDown; + char mIgnoreSliders; + + }; + +#endif diff --git a/gameSource/TimbrePicker.cpp b/gameSource/TimbrePicker.cpp new file mode 100644 index 0000000..290ec5b --- /dev/null +++ b/gameSource/TimbrePicker.cpp @@ -0,0 +1,18 @@ +#include "TimbrePicker.h" + +// all share one stack +SimpleVector globalTimbreStack; + + +TimbrePicker::TimbrePicker( + double inAnchorX, double inAnchorY ) + : ResourcePicker( inAnchorX, inAnchorY, + &globalTimbreStack ) { + } + + + + +TimbrePicker::~TimbrePicker() { + + } diff --git a/gameSource/TimbrePicker.h b/gameSource/TimbrePicker.h new file mode 100644 index 0000000..39d1593 --- /dev/null +++ b/gameSource/TimbrePicker.h @@ -0,0 +1,40 @@ +#ifndef TIMBRE_PICKER_INCLUDED +#define TIMBRE_PICKER_INCLUDED + +#include "TimbreResource.h" +#include "ResourcePicker.h" +// must include this to avoid linker errors +#include "ResourcePicker.cpp" + + +class TimbrePicker : public ResourcePicker { + + + public: + + + + /** + * Constructs a picker. + * + * @param inAnchorX the x position of the upper left corner + * of this component. + * @param inAnchorY the y position of the upper left corner + * of this component. + * + * Sets its own width and height automatically. + */ + TimbrePicker( double inAnchorX, double inAnchorY ); + + + + virtual ~TimbrePicker(); + + }; + + + +#endif + + + diff --git a/gameSource/TimbreResource.cpp b/gameSource/TimbreResource.cpp new file mode 100644 index 0000000..cfcc3d8 --- /dev/null +++ b/gameSource/TimbreResource.cpp @@ -0,0 +1,526 @@ +#include "TimbreResource.h" +#include "resourceManager.h" + +#include "imageCache.h" + +#include "packSaver.h" + +#include "minorGems/util/log/AppLog.h" + + + +Color harmonicLowColor( 0, 85.0/255, 128.0/255 ); +Color harmonicHighColor( 0, 170.0/255, 1.0 ); + + + + +// static inits +TimbreResource *TimbreResource::sBlankTimbre = NULL; + +void TimbreResource::staticInit() { + sBlankTimbre = new TimbreResource(); + // ensure that blank timbre is saved at least once + sBlankTimbre->finishEdit(); + } + +void TimbreResource::staticFree() { + delete sBlankTimbre; + } + + + + +void TimbreResource::setupDefault() { + memset( (void *)mHarmonicLevels, 0, F ); + + mAttack = 0; + mHold = 255; + mRelease = 0; + + mOctavesDown = 1; + + + const char *defaultName = "default"; + memcpy( mName, defaultName, strlen( defaultName ) + 1 ); + + makeUniqueID(); + } + + + +TimbreResource::TimbreResource() { + setupDefault(); + } + + + +char TimbreResource::initFromData( unsigned char *inData, int inLength ) { + if( inLength >= mDataLength ) { + + memcpy( (void *)mHarmonicLevels, inData, F ); + + inLength -= F; + + mAttack = inData[ F ]; + mHold = inData[ F + 1 ]; + mRelease = inData[ F + 2 ]; + mOctavesDown = inData[ F + 3 ]; + + inLength -= 4; + + + // remainder is name + if( inLength <= 11 ) { + memcpy( mName, &inData[mDataLength], inLength ); + return true; + } + } + return false; + } + + + +TimbreResource::TimbreResource( uniqueID inID, + unsigned char *inData, int inLength ) + : mID( inID ) { + + if( !initFromData( inData, inLength ) ) { + // fail + setupDefault(); + } + } + + + + +TimbreResource::TimbreResource( uniqueID inID ) + : mID( inID ) { + + int length; + char fromNetwork; + + unsigned char *data = loadResourceData( "timbre", mID, &length, + &fromNetwork ); + + if( data != NULL ) { + + if( ! initFromData( data, length ) ) { + // failure + setupDefault(); + } + + delete [] data; + data = NULL; + + if( fromNetwork ) { + // save to disk using the ID that we fetched it with + finishEdit( false ); + } + } + else { + // music failed to load + setupDefault(); + } + + if( data != NULL ) { + delete [] data; + } + } + + + +void TimbreResource::editHarmonic( int inHarmonic, int inLevel ) { + mHarmonicLevels[inHarmonic] = (unsigned char)inLevel; + } + + + +int TimbreResource::getHarmonicLevel( int inHarmonic ) { + return mHarmonicLevels[inHarmonic]; + } + + + +void TimbreResource::editEnvelope( int inAttack, int inHold, + int inRelease ) { + mAttack = (unsigned char)inAttack; + mHold = (unsigned char)inHold; + mRelease = (unsigned char)inRelease; + } + + +void TimbreResource::editOctavesDown( int inOctavesDown ) { + mOctavesDown = inOctavesDown; + } + + + +int TimbreResource::getAttack() { + return mAttack; + } + +int TimbreResource::getHold() { + return mHold; + } + +int TimbreResource::getRelease() { + return mRelease; + } + + +int TimbreResource::getOctavesDown() { + return mOctavesDown; + } + + + +rgbaColor TimbreResource::getTimbreColor() { + + float r, g, b; + r = 0; + g = 0; + b = 0; + + for( int y=0; yr; + g += mHarmonicLevels[y] * c->g; + b += mHarmonicLevels[y] * c->b; + + delete c; + } + + // normalize + float max = 0; + if( r>max ) { + max = r; + } + if( g>max ) { + max = g; + } + if( b>max ) { + max = b; + } + if( max == 0 ) { + // default color for no harmonics + r = 1.0; + g = 0; + b = 0; + } + else { + r /= max; + g /= max; + b /= max; + } + + + double octaveFade = 1.0 - mOctavesDown * 0.05; + + r *= octaveFade; + g *= octaveFade; + b *= octaveFade; + + rgbaColor returnVal = { { (unsigned char)(r * 255), + (unsigned char)(g * 255), + (unsigned char)(b * 255) } }; + + return returnVal; + } + + + + + + +void TimbreResource::editTimbreName( const char *inName ) { + int newLength = strlen( inName ); + if( newLength > 10 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: TimbreResource name %s is too long (10 max)\n", inName ); + } + else { + memcpy( mName, inName, newLength + 1 ); + } + } + + +#include "minorGems/util/stringUtils.h" + + +char *TimbreResource::getTimbreName() { + return stringDuplicate( mName ); + } + + + +#include "minorGems/util/SimpleVector.h" + + +unsigned char *TimbreResource::makeBytes( int *outLength ) { + SimpleVector dataAccum; + + dataAccum.push_back( mHarmonicLevels, F ); + + dataAccum.push_back( mAttack ); + dataAccum.push_back( mHold ); + dataAccum.push_back( mRelease ); + dataAccum.push_back( mOctavesDown ); + + dataAccum.push_back( (unsigned char *)mName, + strlen( mName ) + 1 ); + + + *outLength = dataAccum.size(); + + return dataAccum.getElementArray(); + } + + + + +void TimbreResource::finishEdit( char inGenerateNewID ) { + uniqueID oldID = mID; + + if( inGenerateNewID ) { + makeUniqueID(); + } + + + if( ! equal( oldID, mID ) || + ! resourceExists( "timbre", mID ) ) { + + // change + + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + saveResourceData( "timbre", mID, + mName, + data, dataSize ); + + delete [] data; + } + } + + + +void TimbreResource::saveToPack() { + int dataSize; + unsigned char *data = makeBytes( &dataSize ); + + addToPack( "timbre", mID, + mName, + data, dataSize ); + + delete [] data; + } + + + + +uniqueID TimbreResource::getUniqueID() { + return mID; + } + + + +const char *TimbreResource::getResourceType() { + return "timbre"; + } + +TimbreResource TimbreResource::getDefaultResource() { + return *( TimbreResource::sBlankTimbre ); + } + + + + +void TimbreResource::makeUniqueID() { + + partialUniqueID p = startUniqueID(); + + + p = addToUniqueID( p, mHarmonicLevels, F ); + + p = addToUniqueID( p, &mAttack, 1 ); + p = addToUniqueID( p, &mHold, 1 ); + p = addToUniqueID( p, &mRelease, 1 ); + p = addToUniqueID( p, &mOctavesDown, 1 ); + + + p = addToUniqueID( p, (unsigned char*)mName, strlen( mName ) + 1 ); + + mID = ::makeUniqueID( p ); + } + + + +Image *TimbreResource::getImage() { + Image *image = new Image( FL, F, 3, true ); + + double *channels[3]; + + int i; + + for( i=0; i<3; i++ ) { + channels[i] = image->getChannel( i ); + } + + //Color *drawColorA = &harmonicLowColor; + //Color *drawColorB = &harmonicHighColor; + + rgbaColor fullHotC = getTimbreColor(); + rgbaColor fullC = fullHotC; + fullC.comp.r = (unsigned char)( fullC.comp.r * 0.50 ); + fullC.comp.g = (unsigned char)( fullC.comp.g * 0.50 ); + fullC.comp.b = (unsigned char)( fullC.comp.b * 0.50 ); + + + Color drawColorA( fullC.comp.r / 255.0f, + fullC.comp.g / 255.0f, + fullC.comp.b / 255.0f ); + Color drawColorB( fullHotC.comp.r / 255.0f, + fullHotC.comp.g / 255.0f, + fullHotC.comp.b / 255.0f ); + + + + for( int y=0; y x ) { + Color *levelColor = Color::linearSum( &drawColorB, &drawColorA, + x / (float)FL ); + for( i=0; i<3; i++ ) { + channels[i][pixel] = (*levelColor)[i]; + } + delete levelColor; + } + + } + } + + return image; + } + + + + +Sprite *TimbreResource::getSprite( char inUseTrans, char inCacheOK ) { + // ignore inUseTrans + + if( inCacheOK ) { + + Image *cachedImage = getCachedImage( mID, false ); + + if( cachedImage == NULL ) { + cachedImage = getImage(); + + addCachedImage( mID, false, cachedImage ); + } + return new Sprite( cachedImage ); + } + else { + // ignore cache + Image *image = getImage(); + + Sprite *sprite = new Sprite( image ); + delete image; + return sprite; + } + } + + + +void TimbreResource::print() { + char *idString = getHumanReadableString( mID ); + + printf( "TimbreResource %s:\n", idString ); + + delete [] idString; + + printf( " name: %s\n", mName ); + + printf( " " ); + for( int h=0; h 0 ) { + numUsed = i+1; + } + //printf( "coef %d = %f\n", i, coeffs[i] ); + + } + + setTimbre( inTimbreNumber, coeffs, numUsed, mOctavesDown ); + } + } + + + diff --git a/gameSource/TimbreResource.h b/gameSource/TimbreResource.h new file mode 100644 index 0000000..3db4428 --- /dev/null +++ b/gameSource/TimbreResource.h @@ -0,0 +1,144 @@ +#ifndef TIMBRE_RESOURCE_INCLUDED +#define TIMBRE_RESOURCE_INCLUDED + + +#include "color.h" +#include "common.h" +#include "uniqueID.h" +#include "Sprite.h" +#include "musicPlayer.h" + + +// number of harmonics +#define F 16 +// number of levels for a harmonic, including 0 +#define FL 16 + + +class TimbreResource { + public: + + // blank music + TimbreResource(); + + + // timbre loaded from resource manager + TimbreResource( uniqueID inID ); + + // timbre loaded from data string + TimbreResource( uniqueID inID, unsigned char *inData, int inLength ); + + + // changes the level of a harmonic + // harmonic from 0..15 + // level from 0..16 + void editHarmonic( int inHarmonic, int inLevel ); + + + int getHarmonicLevel( int inHarmonic ); + + + // values in 0..255 + void editEnvelope( int inAttack, int inHold, + int inRelease ); + + void editOctavesDown( int inOctavesDown ); + + + int getAttack(); + int getHold(); + int getRelease(); + + int getOctavesDown(); + + + // this timbre as a color, for easy differentiation in displays + rgbaColor getTimbreColor(); + + + + // name has at most 10 chars + void editTimbreName( const char *inName ); + + + // destroyed by caller + char *getTimbreName(); + + + // finishes the edit, generates a new unique ID, saves result + void finishEdit( char inGenerateNewID=true ); + + // recursively saves to current resource pack + void saveToPack(); + + + // get an image of this timbre + Image *getImage(); + + + // implements ResourceType functions as needed by ResourcePicker + uniqueID getUniqueID(); + Sprite *getSprite( char inUseTrans=false, char inCacheOK=true ); + static const char *getResourceType(); + static TimbreResource getDefaultResource(); + + char *getName() { + return getTimbreName(); + } + + + // sets this timbre into music player + void setInPlayer( int inTimbreNumber, + char inUpdatePlayerTimbre, + char inUpdatePlayerEnvelope ); + + + + + void print(); + + + // blank timbre (pure sine) + static TimbreResource *sBlankTimbre; + + static void staticInit(); + static void staticFree(); + + protected: + + void setupDefault(); + + + char initFromData( unsigned char *inData, int inLength ); + + + // result destroyed by caller + unsigned char *makeBytes( int *outLength ); + + + void makeUniqueID(); + + + unsigned char mHarmonicLevels[F]; + + unsigned char mAttack; + unsigned char mHold; + unsigned char mRelease; + + // 0 to use base frequency + // positive numbers to go down by octaves + unsigned char mOctavesDown; + + + const static int mDataLength = F + 4; + + char mName[11]; + + + uniqueID mID; + + + }; + + +#endif diff --git a/gameSource/TimerDisplay.cpp b/gameSource/TimerDisplay.cpp new file mode 100644 index 0000000..fff6c4e --- /dev/null +++ b/gameSource/TimerDisplay.cpp @@ -0,0 +1,128 @@ +#include "TimerDisplay.h" +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/util/stringUtils.h" + + +extern TextGL *largeTextFixed; + + + +TimerDisplay::TimerDisplay( double inAnchorX, double inAnchorY ) + : ToolTipComponentGL( inAnchorX, inAnchorY, 16, 8 ), + mTime( 0 ), + mBlinkCycle( 0 ), + mNegativeTipSet( false ), + mFrozen( false ) { + + setToolTip( "tip_timer" ); + } + + + +TimerDisplay::~TimerDisplay() { + } + + +extern char isControllerGame; + +extern char *addressString; + + +#include "minorGems/util/TranslationManager.h" + + +void TimerDisplay::freeze( char inFrozen ) { + char oldFrozen = mFrozen; + mFrozen = inFrozen; + + if( mFrozen && !oldFrozen ) { + if( ! isControllerGame || addressString == NULL ) { + setToolTip( "tip_noConnection" ); + } + else { + // put waiting address in tip + char *tip = autoSprintf( "%s %s", + TranslationManager::translate( + "tip_noConnection_address" ), + addressString ); + setToolTip( tip ); + + delete [] tip; + } + } + else if( !mFrozen && oldFrozen ) { + if( mNegativeTipSet ) { + setToolTip( "tip_waiting" ); + } + else { + setToolTip( "tip_timer" ); + } + } + } + + + +void TimerDisplay::setTime( int inSecondsLeft ) { + mTime = inSecondsLeft; + + if( mTime <= 0 && !mNegativeTipSet ) { + if( !mFrozen ) { // frozen tip overrides + setToolTip( "tip_waiting" ); + mNegativeTipSet = true; + } + } + else if( mTime > 0 && mNegativeTipSet ) { + if( !mFrozen ) { // frozen tip overrides + setToolTip( "tip_timer" ); + mNegativeTipSet = false; + } + } + + } + + + +void TimerDisplay::fireRedraw() { + + char *timeString = autoSprintf( "%d", mTime ); + + + // right aligned + + int drawWidth = strlen( timeString ) * 8; + + + Color *oldColor = largeTextFixed->getFontColor()->copy(); + + Color *tempColor = oldColor->copy(); + + // timer becomes redder during last 10 seconds + if( mTime <= 10 ) { + tempColor->g = (mTime - 5) / 5.0; + tempColor->b = (mTime - 5) / 5.0; + } + + if( mTime <= 5 && mTime > 0 ) { + mBlinkCycle ++; + + tempColor->a = 0.5 * sin( mBlinkCycle / 2.0 ) + 0.5; + } + if( mTime == 0 ) { + // reset + mBlinkCycle = 0; + } + + largeTextFixed->setFontColor( tempColor ); + + + + largeTextFixed->drawText( timeString, mAnchorX + mWidth - drawWidth, + mAnchorY, drawWidth, 8 ); + + largeTextFixed->setFontColor( oldColor ); + + + delete [] timeString; + } + + diff --git a/gameSource/TimerDisplay.h b/gameSource/TimerDisplay.h new file mode 100644 index 0000000..63b7e9c --- /dev/null +++ b/gameSource/TimerDisplay.h @@ -0,0 +1,38 @@ +#ifndef TIMER_DISPLAY_INCLUDED +#define TIMER_DISPLAY_INCLUDED + + +#include "ToolTipComponentGL.h" + + + +class TimerDisplay : public ToolTipComponentGL { + + + public: + TimerDisplay( double inAnchorX, double inAnchorY ); + + virtual ~TimerDisplay(); + + + void setTime( int inSecondsLeft ); + + void freeze( char inFrozen ); + + + + // override + virtual void fireRedraw(); + + protected: + int mTime; + + int mBlinkCycle; + + char mNegativeTipSet; + + char mFrozen; + }; + + +#endif diff --git a/gameSource/ToolTipComponentGL.cpp b/gameSource/ToolTipComponentGL.cpp new file mode 100644 index 0000000..c735cbf --- /dev/null +++ b/gameSource/ToolTipComponentGL.cpp @@ -0,0 +1,95 @@ +#include "ToolTipComponentGL.h" +#include "ToolTipManager.h" + +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/stringUtils.h" + +#include + + +ToolTipComponentGL::ToolTipComponentGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : GUIComponentGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mHover( false ), mTip( NULL ) { + } + + + +ToolTipComponentGL::~ToolTipComponentGL() { + if( mTip != NULL ) { + delete [] mTip; + } + } + + + +void ToolTipComponentGL::setToolTip( const char *inTipTranslationKey ) { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + + if( inTipTranslationKey != NULL ) { + char *string = + (char *)TranslationManager::translate( + (char*)inTipTranslationKey ); + + mTip = stringDuplicate( string ); + } + } + + + +void ToolTipComponentGL::mouseMoved( double inX, double inY ) { + char oldHover = mHover; + + + GUIComponentGL::mouseMoved( inX, inY ); + + if( !isInside( inX, inY ) || !isEnabled() ) { + // the mouse has been moved outside of us or we're disabled, + // so un-hover + mHover = false; + } + else if( isEnabled() ){ + // moved back in, and we're enabled, so re-hover + mHover = true; + } + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + + } + + + +void ToolTipComponentGL::mouseDragged( double inX, double inY ) { + char oldHover = mHover; + + GUIComponentGL::mouseMoved( inX, inY ); + + if( !isInside( inX, inY ) || !isEnabled() ) { + // the mouse has been dragged outside of us or we're disabled + mHover = false; + } + else if( isEnabled() ){ + // dragged back in, and we're enabled + mHover = true; + } + + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering, or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + } diff --git a/gameSource/ToolTipComponentGL.h b/gameSource/ToolTipComponentGL.h new file mode 100644 index 0000000..2813c3a --- /dev/null +++ b/gameSource/ToolTipComponentGL.h @@ -0,0 +1,29 @@ +#ifndef TOOL_TIP_COMPONENT_GL_INCLUDED +#define TOOL_TIP_COMPONENT_GL_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" + +class ToolTipComponentGL : public GUIComponentGL { + public: + ToolTipComponentGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual ~ToolTipComponentGL(); + + // defaults to no tip + // automatically invokes TranslationManager on passed-in key + virtual void setToolTip( const char *inTipTranslationKey ); + + + // override functions in GUIComponentGL + virtual void mouseMoved( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + + protected: + char mHover; + char *mTip; + }; + + +#endif diff --git a/gameSource/ToolTipDisplay.cpp b/gameSource/ToolTipDisplay.cpp new file mode 100644 index 0000000..5672e8b --- /dev/null +++ b/gameSource/ToolTipDisplay.cpp @@ -0,0 +1,87 @@ +#include "ToolTipDisplay.h" +#include "ToolTipManager.h" +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/util/stringUtils.h" + + +extern TextGL *largeText; + + + +ToolTipDisplay::ToolTipDisplay( double inAnchorX, double inAnchorY ) + : GUIComponentGL( inAnchorX, inAnchorY, 224, 8 ), + mLastTip( NULL ), mFadeFactor( 0 ) { + } + + + +ToolTipDisplay::~ToolTipDisplay() { + if( mLastTip != NULL ) { + delete [] mLastTip; + } + } + + + +char *ToolTipDisplay::getTip() { + return ToolTipManager::getTip(); + } + + + + +void ToolTipDisplay::fireRedraw() { + + + char *tip = getTip(); + + + if( tip != NULL ) { + + if( mLastTip == NULL ) { + mLastTip = stringDuplicate( tip ); + } + else { + if( strcmp( mLastTip, tip ) != 0 ) { + // tip changed + delete [] mLastTip; + mLastTip = stringDuplicate( tip ); + } + } + + mFadeFactor = 1; + } + else { + mFadeFactor -= 0.02; + + if( mFadeFactor < 0 ) { + mFadeFactor = 0; + + if( mLastTip != NULL ) { + delete [] mLastTip; + mLastTip = NULL; + } + } + } + + + if( mLastTip != NULL ) { + // left aligned + + int drawWidth = strlen( mLastTip ) * 8; + + + Color *oldColor = largeText->getFontColor()->copy(); + + Color *tempColor = oldColor->copy(); + tempColor->a = mFadeFactor; + largeText->setFontColor( tempColor ); + + largeText->drawText( mLastTip, mAnchorX, + mAnchorY, drawWidth, 8 ); + + largeText->setFontColor( oldColor ); + } + } + + diff --git a/gameSource/ToolTipDisplay.h b/gameSource/ToolTipDisplay.h new file mode 100644 index 0000000..c7d6a8c --- /dev/null +++ b/gameSource/ToolTipDisplay.h @@ -0,0 +1,34 @@ +#ifndef TOOL_TIP_DISPLAY_INCLUDED +#define TOOL_TIP_DISPLAY_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIComponentGL.h" + + + +class ToolTipDisplay : public GUIComponentGL { + + + public: + ToolTipDisplay( double inAnchorX, double inAnchorY ); + + virtual ~ToolTipDisplay(); + + // override + virtual void fireRedraw(); + + protected: + + // result NOT to be detroyed by caller + // subclasses can override this to control where tip comes from + virtual char *getTip(); + + + char *mLastTip; + + double mFadeFactor; + + }; + + +#endif diff --git a/gameSource/ToolTipManager.cpp b/gameSource/ToolTipManager.cpp new file mode 100644 index 0000000..2826d80 --- /dev/null +++ b/gameSource/ToolTipManager.cpp @@ -0,0 +1,49 @@ +#include "ToolTipManager.h" + +#include "minorGems/util/stringUtils.h" + + +char *ToolTipManager::mTip = NULL; +char ToolTipManager::mFrozen = false; + + +void ToolTipManager::setTip( const char *inTip ) { + if( mFrozen ) { + return; + } + + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + + if( inTip != NULL ) { + mTip = stringDuplicate( inTip ); + } + } + + +char *ToolTipManager::getTip() { + return mTip; + } + + + +void ToolTipManager::freeze( char inFrozen ) { + mFrozen = inFrozen; + } + + + + +void ToolTipManager::staticInit() { + } + + +void ToolTipManager::staticFree() { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + } + diff --git a/gameSource/ToolTipManager.h b/gameSource/ToolTipManager.h new file mode 100644 index 0000000..be002ef --- /dev/null +++ b/gameSource/ToolTipManager.h @@ -0,0 +1,29 @@ + + +class ToolTipManager { + + + public: + + // can be NULL, copied internally, so destroyed by caller + static void setTip( const char *inTip ); + + + // destroyed by this class + static char *getTip(); + + + // setTip calls have no effect until unfrozen + static void freeze( char inFrozen ); + + + + static void staticInit(); + static void staticFree(); + + + private: + static char *mTip; + + static char mFrozen; + }; diff --git a/gameSource/ToolTipSliderGL.cpp b/gameSource/ToolTipSliderGL.cpp new file mode 100644 index 0000000..dc4c7fd --- /dev/null +++ b/gameSource/ToolTipSliderGL.cpp @@ -0,0 +1,112 @@ +#include "ToolTipSliderGL.h" +#include "ToolTipManager.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/TranslationManager.h" + + + +ToolTipSliderGL::ToolTipSliderGL( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Image *inIconImage, + double inIconWidthFraction, + Color *inBarStartColor, Color *inBarEndColor, + Color *inThumbColor, Color *inBorderColor, + double inBorderWidth, + double inThumbWidth, + double inMinThumbIncrement ) + : SliderGL( + inAnchorX, inAnchorY, inWidth, + inHeight, inIconImage, + inIconWidthFraction, + inBarStartColor, inBarEndColor, + inThumbColor, inBorderColor, + inBorderWidth, + inThumbWidth, + inMinThumbIncrement), + mHover( false ), + mTip( NULL ) { + } + + + +ToolTipSliderGL::~ToolTipSliderGL() { + if( mTip != NULL ) { + delete [] mTip; + } + } + + + +void ToolTipSliderGL::setToolTip( const char *inTipTranslationKey, + char inUseTranslationManager ) { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + + if( inTipTranslationKey != NULL ) { + char *string = (char *)inTipTranslationKey; + + if( inUseTranslationManager ) { + string = (char *)TranslationManager::translate( + (char*)inTipTranslationKey ); + } + + mTip = stringDuplicate( string ); + } + } + + + +void ToolTipSliderGL::mouseMoved( double inX, double inY ) { + char oldHover = mHover; + + SliderGL::mouseMoved( inX, inY ); + + mHover = isInside( inX, inY ); + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + } + + + +void ToolTipSliderGL::mouseDragged( double inX, double inY ) { + char oldHover = mHover; + + SliderGL::mouseDragged( inX, inY ); + + mHover = isInside( inX, inY ); + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering, or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + } + + + +void ToolTipSliderGL::mouseReleased( double inX, double inY ) { + SliderGL::mouseReleased( inX, inY ); + + // always turn tip off after release inside button + // this ensures that tip disappears even if button is completely removed + // (not just disabled) as a result of the button push + + // if user moves mouse around in button again, tip will reappear anyway, + // so it's okay. + if( isInside( inX, inY ) ) { + // hide tip + ToolTipManager::setTip( NULL ); + } + } diff --git a/gameSource/ToolTipSliderGL.h b/gameSource/ToolTipSliderGL.h new file mode 100644 index 0000000..4519fd3 --- /dev/null +++ b/gameSource/ToolTipSliderGL.h @@ -0,0 +1,41 @@ +#ifndef TOOL_TIP_SLIDER_INCLUDED +#define TOOL_TIP_SLIDER_INCLUDED + + +#include "minorGems/graphics/openGL/gui/SliderGL.h" + + +class ToolTipSliderGL : public SliderGL { + public: + + ToolTipSliderGL( + double inAnchorX, double inAnchorY, double inWidth, + double inHeight, Image *inIconImage, + double inIconWidthFraction, + Color *inBarStartColor, Color *inBarEndColor, + Color *inThumbColor, Color *inBorderColor, + double inBorderWidth, + double inThumbWidth, + double inMinThumbIncrement ); + + virtual ~ToolTipSliderGL(); + + // defaults to no tip + // automatically invokes TranslationManager on passed-in key + // if inUseTranslationManager is false, uses Key as tool tip directly + virtual void setToolTip( const char *inTipTranslationKey, + char inUseTranslationManager = true ); + + + // override functions in SliderGL + virtual void mouseMoved( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + + protected: + char mHover; + char *mTip; + }; + + +#endif diff --git a/gameSource/TransformToolSet.cpp b/gameSource/TransformToolSet.cpp new file mode 100644 index 0000000..4bc34c4 --- /dev/null +++ b/gameSource/TransformToolSet.cpp @@ -0,0 +1,88 @@ +#include "TransformToolSet.h" + + + +TransformToolSet::TransformToolSet( double inAnchorX, double inAnchorY, + char inHasColorize ) + : GUIPanelGL( inAnchorX, inAnchorY, 16, 100, + new Color( 0, 0, 0, 0 ) ), + mLastPressed( flipH ) { + + + + mFlipHButton = new FlipHButtonGL( + inAnchorX, inAnchorY, 16, 16 ); + + mFlipVButton = new FlipVButtonGL( + inAnchorX, inAnchorY + 18, 16, 16 ); + + mCCWButton = new RotateCCWButtonGL( + inAnchorX, inAnchorY + 36, 16, 16 ); + + mCWButton = new RotateCWButtonGL( + inAnchorX, inAnchorY + 54, 16, 16 ); + + if( inHasColorize ) { + mColorizeButton = new ColorizeButtonGL( + inAnchorX, inAnchorY + 72, 16, 16 ); + + mClearButton = new ClearButtonGL( + inAnchorX, inAnchorY + 90, 16, 16 ); + } + else { + mColorizeButton = NULL; + mClearButton = new ClearButtonGL( + inAnchorX, inAnchorY + 72, 16, 16 ); + } + + + add( mFlipHButton ); + add( mFlipVButton ); + add( mCCWButton ); + add( mCWButton ); + add( mClearButton ); + + + + mFlipHButton->addActionListener( this ); + mFlipVButton->addActionListener( this ); + mCCWButton->addActionListener( this ); + mCWButton->addActionListener( this ); + mClearButton->addActionListener( this ); + + if( mColorizeButton != NULL ) { + add( mColorizeButton ); + mColorizeButton->addActionListener( this ); + } + } + + + +transformTool TransformToolSet::getLastPressed() { + return mLastPressed; + } + + + +void TransformToolSet::actionPerformed( GUIComponent *inTarget ) { + if( inTarget == mFlipHButton ) { + mLastPressed = flipH; + } + else if( inTarget == mFlipVButton ) { + mLastPressed = flipV; + } + else if( inTarget == mCCWButton ) { + mLastPressed = rotateCCW; + } + else if( inTarget == mCWButton ) { + mLastPressed = rotateCW; + } + else if( inTarget == mClearButton ) { + mLastPressed = clear; + } + else if( inTarget == mColorizeButton ) { + mLastPressed = colorize; + } + + fireActionPerformed( this ); + } diff --git a/gameSource/TransformToolSet.h b/gameSource/TransformToolSet.h new file mode 100644 index 0000000..fd2852d --- /dev/null +++ b/gameSource/TransformToolSet.h @@ -0,0 +1,47 @@ +#ifndef TRANSFORM_TOOL_SET_INCLUDED +#define TRANSFORM_TOOL_SET_INCLUDED + + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/ui/event/ActionListener.h" +#include "minorGems/ui/event/ActionListenerList.h" + +#include "buttons.h" + + +enum transformTool { flipV, flipH, rotateCCW, rotateCW, clear, colorize }; + + +class TransformToolSet : public GUIPanelGL, public ActionListener, + public ActionListenerList { + + + public: + + // sets its width/height automatically + TransformToolSet( double inAnchorX, double inAnchorY, + char inHasColorize = false ); + + + transformTool getLastPressed(); + + // implements ActionListener + virtual void actionPerformed( GUIComponent *inTarget ); + + protected: + + transformTool mLastPressed; + + FlipHButtonGL *mFlipHButton; + FlipVButtonGL *mFlipVButton; + + RotateCCWButtonGL *mCCWButton; + RotateCWButtonGL *mCWButton; + + ClearButtonGL *mClearButton; + + ColorizeButtonGL *mColorizeButton; + }; + + +#endif diff --git a/gameSource/WarningDisplay.cpp b/gameSource/WarningDisplay.cpp new file mode 100644 index 0000000..2d7e865 --- /dev/null +++ b/gameSource/WarningDisplay.cpp @@ -0,0 +1,70 @@ +#include "WarningDisplay.h" + + + +WarningDisplay::WarningDisplay( double inAnchorX, double inAnchorY ) + : ToolTipComponentGL( inAnchorX, inAnchorY, 16, 16 ), + mBlinkCycle( 0 ), + mFramesLeft( 0 ), + mSprite( "warning.tga", true ) { + + setEnabled( false ); + } + + + +WarningDisplay::~WarningDisplay() { + } + + +extern char isControllerGame; + +#include "minorGems/network/HostAddress.h" +#include "minorGems/util/TranslationManager.h" + + +void WarningDisplay::show( const char *inToolTipTransKey, int inFrames ) { + setToolTip( inToolTipTransKey ); + + setEnabled( true ); + + mBlinkCycle = 0; + mFramesLeft = inFrames; + } + + + +void WarningDisplay::fireRedraw() { + if( mEnabled ) { + mBlinkCycle ++; + + mFramesLeft --; + + + + double fade = 0.5 * sin( mBlinkCycle / 2.0 ) + 0.5; + + if( mFramesLeft < 30 ) { + // fade out gently for last second + fade *= (double)mFramesLeft / 30; + } + + + Vector3D pos( mAnchorX + mWidth / 2, + mAnchorY + mHeight / 2, + 0 ); + + + + mSprite.draw( 0, 0, &pos, + 1, + fade ); + + if( mFramesLeft == 0 ) { + setEnabled( false ); + } + } + + } + + diff --git a/gameSource/WarningDisplay.h b/gameSource/WarningDisplay.h new file mode 100644 index 0000000..43abe39 --- /dev/null +++ b/gameSource/WarningDisplay.h @@ -0,0 +1,33 @@ +#ifndef WARNING_DISPLAY_INCLUDED +#define WARNING_DISPLAY_INCLUDED + + +#include "ToolTipComponentGL.h" +#include "Sprite.h" + + + +class WarningDisplay : public ToolTipComponentGL { + + + public: + WarningDisplay( double inAnchorX, double inAnchorY ); + + virtual ~WarningDisplay(); + + + // shows the flashing warning for a certain number of frames + void show( const char *inToolTipTransKey, int inFrames ); + + // override + virtual void fireRedraw(); + + protected: + int mBlinkCycle; + int mFramesLeft; + + Sprite mSprite; + }; + + +#endif diff --git a/gameSource/act.png b/gameSource/act.png new file mode 100644 index 0000000..4d193ed Binary files /dev/null and b/gameSource/act.png differ diff --git a/gameSource/actionBoxEnd.png b/gameSource/actionBoxEnd.png new file mode 100644 index 0000000..5915093 Binary files /dev/null and b/gameSource/actionBoxEnd.png differ diff --git a/gameSource/actionBoxEndFlip.png b/gameSource/actionBoxEndFlip.png new file mode 100644 index 0000000..a4d2b46 Binary files /dev/null and b/gameSource/actionBoxEndFlip.png differ diff --git a/gameSource/actionBoxEndFlipTall.png b/gameSource/actionBoxEndFlipTall.png new file mode 100644 index 0000000..f02b806 Binary files /dev/null and b/gameSource/actionBoxEndFlipTall.png differ diff --git a/gameSource/actionBoxEndTall.png b/gameSource/actionBoxEndTall.png new file mode 100644 index 0000000..2917f07 Binary files /dev/null and b/gameSource/actionBoxEndTall.png differ diff --git a/gameSource/actionBoxMiddle.png b/gameSource/actionBoxMiddle.png new file mode 100644 index 0000000..f83cbc8 Binary files /dev/null and b/gameSource/actionBoxMiddle.png differ diff --git a/gameSource/actionBoxMiddleExtra.png b/gameSource/actionBoxMiddleExtra.png new file mode 100644 index 0000000..25456b1 Binary files /dev/null and b/gameSource/actionBoxMiddleExtra.png differ diff --git a/gameSource/actionBoxMiddleExtraTall.png b/gameSource/actionBoxMiddleExtraTall.png new file mode 100644 index 0000000..8adbc5d Binary files /dev/null and b/gameSource/actionBoxMiddleExtraTall.png differ diff --git a/gameSource/actionBoxMiddleTall.png b/gameSource/actionBoxMiddleTall.png new file mode 100644 index 0000000..b85902e Binary files /dev/null and b/gameSource/actionBoxMiddleTall.png differ diff --git a/gameSource/actionBoxStart.png b/gameSource/actionBoxStart.png new file mode 100644 index 0000000..5e8643b Binary files /dev/null and b/gameSource/actionBoxStart.png differ diff --git a/gameSource/actionBoxStartTall.png b/gameSource/actionBoxStartTall.png new file mode 100644 index 0000000..b4b438c Binary files /dev/null and b/gameSource/actionBoxStartTall.png differ diff --git a/gameSource/addObject.png b/gameSource/addObject.png new file mode 100644 index 0000000..e408138 Binary files /dev/null and b/gameSource/addObject.png differ diff --git a/gameSource/anchor.png b/gameSource/anchor.png new file mode 100644 index 0000000..85f2626 Binary files /dev/null and b/gameSource/anchor.png differ diff --git a/gameSource/bubbleBottom.png b/gameSource/bubbleBottom.png new file mode 100644 index 0000000..5f0fce9 Binary files /dev/null and b/gameSource/bubbleBottom.png differ diff --git a/gameSource/bubbleBottomFlip.png b/gameSource/bubbleBottomFlip.png new file mode 100644 index 0000000..23fd3ec Binary files /dev/null and b/gameSource/bubbleBottomFlip.png differ diff --git a/gameSource/bubbleBottomNoTail.png b/gameSource/bubbleBottomNoTail.png new file mode 100644 index 0000000..a8d72fb Binary files /dev/null and b/gameSource/bubbleBottomNoTail.png differ diff --git a/gameSource/bubbleMiddle.png b/gameSource/bubbleMiddle.png new file mode 100644 index 0000000..c67be97 Binary files /dev/null and b/gameSource/bubbleMiddle.png differ diff --git a/gameSource/bubbleMiddleExtra.png b/gameSource/bubbleMiddleExtra.png new file mode 100644 index 0000000..1089caf Binary files /dev/null and b/gameSource/bubbleMiddleExtra.png differ diff --git a/gameSource/bubbleMiddleExtraThin.png b/gameSource/bubbleMiddleExtraThin.png new file mode 100644 index 0000000..907d3af Binary files /dev/null and b/gameSource/bubbleMiddleExtraThin.png differ diff --git a/gameSource/bubbleMiddleTail.png b/gameSource/bubbleMiddleTail.png new file mode 100644 index 0000000..1c097b0 Binary files /dev/null and b/gameSource/bubbleMiddleTail.png differ diff --git a/gameSource/bubbleMiddleTailFlip.png b/gameSource/bubbleMiddleTailFlip.png new file mode 100644 index 0000000..b6cc4af Binary files /dev/null and b/gameSource/bubbleMiddleTailFlip.png differ diff --git a/gameSource/bubbleTop.png b/gameSource/bubbleTop.png new file mode 100644 index 0000000..b8067de Binary files /dev/null and b/gameSource/bubbleTop.png differ diff --git a/gameSource/buttons.cpp b/gameSource/buttons.cpp new file mode 100644 index 0000000..fd9ea94 --- /dev/null +++ b/gameSource/buttons.cpp @@ -0,0 +1,1659 @@ +#include "buttons.h" +#include "ToolTipManager.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/log/AppLog.h" + +#include + + + +ToolTipButtonGL::ToolTipButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mTip( NULL ) { + } + + + +ToolTipButtonGL::~ToolTipButtonGL() { + if( mTip != NULL ) { + delete [] mTip; + } + } + + + +void ToolTipButtonGL::setToolTip( const char *inTipTranslationKey, + char inUseTranslationManager ) { + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + } + + if( inTipTranslationKey != NULL ) { + char *string = (char *)inTipTranslationKey; + + if( inUseTranslationManager ) { + string = (char *)TranslationManager::translate( + (char*)inTipTranslationKey ); + } + + mTip = stringDuplicate( string ); + } + } + + + +void ToolTipButtonGL::mouseMoved( double inX, double inY ) { + char oldHover = mHover; + + ButtonGL::mouseMoved( inX, inY ); + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + } + + + +void ToolTipButtonGL::mouseDragged( double inX, double inY ) { + char oldHover = mHover; + + ButtonGL::mouseDragged( inX, inY ); + + if( mHover && ( ! oldHover || ToolTipManager::getTip() == NULL ) ) { + // mouse newly hovering, or tip not set + ToolTipManager::setTip( mTip ); + } + else if( !mHover && oldHover ) { + // mouse newly left + ToolTipManager::setTip( NULL ); + } + } + + + +void ToolTipButtonGL::mouseReleased( double inX, double inY ) { + ButtonGL::mouseReleased( inX, inY ); + + // always turn tip off after release inside button + // this ensures that tip disappears even if button is completely removed + // (not just disabled) as a result of the button push + + // if user moves mouse around in button again, tip will reappear anyway, + // so it's okay. + if( isInside( inX, inY ) ) { + // hide tip + ToolTipManager::setTip( NULL ); + } + } + + + + +//static rgbaColor white = {{255,255,255,255}}; +//static rgbaColor gray = {{128,128,128,255}}; +static rgbaColor gray = {{89,89,89,255}}; +static rgbaColor black = {{0,0,0,255}}; + + +BorderButtonGL::BorderButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ToolTipButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mBorderColor( gray ), mFillColor( black ) { + } + + +void BorderButtonGL::setFillColor( rgbaColor inColor ) { + mFillColor = inColor; + } + +void BorderButtonGL::setBorderColor( rgbaColor inColor ) { + mBorderColor = inColor; + } + + +void BorderButtonGL::drawBorder() { + + // 1 pixel wide + glColor4f( mBorderColor.comp.r / 255.0f, + mBorderColor.comp.g / 255.0f, mBorderColor.comp.b / 255.0f, 1); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + // fill + glColor4f( mFillColor.comp.r / 255.0f, + mFillColor.comp.g / 255.0f, mFillColor.comp.b / 255.0f, 1 ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX + 1, mAnchorY + 1 ); + glVertex2d( mAnchorX + mWidth - 1, mAnchorY + 1 ); + glVertex2d( mAnchorX + mWidth - 1, mAnchorY + mHeight - 1 ); + glVertex2d( mAnchorX + 1 , mAnchorY + mHeight - 1 ); + } + glEnd(); + } + + + +IconButtonGL::IconButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : BorderButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + + +void IconButtonGL::drawPressed() { + drawBorder(); + + glColor4f( 1, 1, 1, 1 ); + + drawIcon(); + } + + +void IconButtonGL::drawUnpressed() { + drawBorder(); + + glColor4f( 1, 1, 1, 1 ); + + drawIcon(); + } + + + + + + + +AddButtonGL::AddButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : IconButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + +void AddButtonGL::drawIcon() { + // a plus + double edgeOffset = mWidth; + if( mHeight > mWidth ) { + edgeOffset = mHeight; + } + edgeOffset *= 0.25; + + // round to a whole number of pixels + edgeOffset = (int)( edgeOffset ); + + + glBegin( GL_QUADS ); { + // 1 pixel wide + glVertex2d( mAnchorX + mWidth/2 - 0.5, mAnchorY + edgeOffset ); + glVertex2d( mAnchorX + mWidth/2 + 0.5, mAnchorY + edgeOffset ); + glVertex2d( mAnchorX + mWidth/2 + 0.5, + mAnchorY + mHeight - edgeOffset ); + glVertex2d( mAnchorX + mWidth/2 - 0.5, + mAnchorY + mHeight - edgeOffset ); + + + glVertex2d( mAnchorX + edgeOffset, mAnchorY + mHeight/2 - 0.5 ); + glVertex2d( mAnchorX + edgeOffset, mAnchorY + mHeight/2 + 0.5 ); + glVertex2d( mAnchorX + mWidth - edgeOffset, + mAnchorY + mHeight/2 + 0.5 ); + glVertex2d( mAnchorX + mWidth - edgeOffset, + mAnchorY + mHeight/2 - 0.5 ); + } + glEnd(); + } + + + + + + + + + + +SpriteButtonGL::SpriteButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : BorderButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mSprite( inSprite ), + mSpriteDrawScale( inSpriteDrawScale ), + mColor( NULL ) { + + } + + + +SpriteButtonGL::~SpriteButtonGL() { + setSprite( NULL ); + + if( mColor != NULL ) { + delete mColor; + } + } + + +void SpriteButtonGL::setColor( Color *inColor ) { + if( mColor != NULL ) { + delete mColor; + } + mColor = inColor; + } + + + + +void SpriteButtonGL::setSprite( Sprite *inSprite ) { + if( mSprite != NULL ) { + delete mSprite; + } + mSprite = inSprite; + } + + +void SpriteButtonGL::drawPressed() { + if( mEnabled ) { + drawBorder(); + + if( mSprite != NULL ) { + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + // a bit darker when pressed + Color c( .75, .75, .75, 1 ); + + if( mColor != NULL ) { + c.r *= mColor->r; + c.g *= mColor->g; + c.b *= mColor->b; + } + + mSprite->draw( 0, 0, &pos, mSpriteDrawScale, 1, &c ); + } + } + } + + + +void SpriteButtonGL::drawUnpressed() { + if( mEnabled ) { + drawBorder(); + + if( mSprite != NULL ) { + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + mSprite->draw( 0, 0, &pos, mSpriteDrawScale, 1, mColor ); + } + } + } + + + + + +ColorButtonGL::ColorButtonGL( rgbaColor inColor, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : BorderButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mLastActionHover( false ) { + + setColor( inColor ); + } + + + +void ColorButtonGL::setColor( rgbaColor inColor, char inForceDarkBorder ) { + setFillColor( inColor ); + + // border now identical to fill + setBorderColor( inColor ); + + /* + + // pick a border color that is guaranteed to be seen + int maxComponent = 0; + + int i; + for( i=0; i<3; i++ ) { + if( maxComponent < inColor.bytes[i] ) { + maxComponent = inColor.bytes[i]; + } + } + + int borderAddition = -50; + + if( maxComponent < 128 && ! inForceDarkBorder) { + borderAddition = 50; + } + + for( i=0; i<3; i++ ) { + int v = (int)( inColor.bytes[i] + borderAddition ); + if( v > 255 ) { + v = 255; + } + if( v < 0 ) { + v = 0; + } + inColor.bytes[i] = (unsigned char)v; + } + + setBorderColor( inColor ); + */ + } + + + +void ColorButtonGL::drawPressed() { + drawBorder(); + } + + + +void ColorButtonGL::drawUnpressed() { + drawBorder(); + } + + +char ColorButtonGL::wasLastActionHover() { + return mLastActionHover; + } + + + +void ColorButtonGL::mousePressed( double inX, double inY ) { + BorderButtonGL::mousePressed( inX, inY ); + + if( isEnabled() ) { + // event + mLastActionHover = false; + fireActionPerformed( this ); + } + } + + + +void ColorButtonGL::mouseDragged( double inX, double inY ) { + BorderButtonGL::mouseDragged( inX, inY ); + + if( isEnabled() && isInside( inX, inY ) ) { + // override pressed behavior, even if press didn't start on us + mPressed = true; + + // fire an event + mLastActionHover = false; + fireActionPerformed( this ); + } + } + + + +void ColorButtonGL::mouseReleased( double inX, double inY ) { + mLastActionHover = false; + //BorderButtonGL::mouseReleased( inX, inY ); + + // always unpress on a release + mPressed = false; + + if( isEnabled() && isInside( inX, inY ) ) { + // fire an event, even if press didn't start on us + fireActionPerformed( this ); + } + + // reset for next time + mPressStartedOnUs = false; + } + + +void ColorButtonGL::mouseMoved( double inX, double inY ) { + BorderButtonGL::mouseMoved( inX, inY ); + + if( isEnabled() && isInside( inX, inY ) ) { + // fire an event + mLastActionHover = true; + fireActionPerformed( this ); + } + } + + + +LeftButtonGL::LeftButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "left.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setBorderColor( black ); + + setToolTip( "tip_prev" ); + } + + + +RightButtonGL::RightButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "right.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setBorderColor( black ); + setToolTip( "tip_next" ); + } + + + + +EditButtonGL::EditButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "edit.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + + +CloseButtonGL::CloseButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : KeyEquivButtonGL( new Sprite( "close.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight, + 'w', 'W') { + } + + + +char CloseButtonGL::isInside( double inX, double inY ) { + // slightly bigger to left and above + if( inX >= mAnchorX - 1 && inX < mAnchorX + mWidth + && inY >= mAnchorY && inY < mAnchorY + mHeight + 1 ) { + + return true; + } + else { + return false; + } + } + + + +NoKeyCloseButtonGL::NoKeyCloseButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "close.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + + +char NoKeyCloseButtonGL::isInside( double inX, double inY ) { + // slightly bigger to left and above + if( inX >= mAnchorX - 1 && inX < mAnchorX + mWidth + && inY >= mAnchorY && inY < mAnchorY + mHeight + 1 ) { + + return true; + } + else { + return false; + } + } + + + +SmallAddButtonGL::SmallAddButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "smallAdd.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + + +QuickDeleteButtonGL::QuickDeleteButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "delete.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + } + + + + +KeyEquivButtonGL::KeyEquivButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight, + char inKeyA, char inKeyB ) + : SpriteButtonGL( inSprite, + inSpriteDrawScale, + inAnchorX, inAnchorY, inWidth, inHeight ), + mKeyA( inKeyA ), mKeyB( inKeyB ) { + } + + + +char KeyEquivButtonGL::isFocused() { + if( !isEnabled() ) { + return false; + } + + return true; + } + + +#include +SDLMod SDLCALL SDL_GetModState(void); + +void KeyEquivButtonGL::keyPressed( unsigned char inKey, + double inX, double inY ) { + if( !isEnabled() ) { + return; + } + + char controlDown = false; + + + SDLMod modState = SDL_GetModState(); + + + if( ( modState & KMOD_CTRL ) + || + ( modState & KMOD_ALT ) + || + ( modState & KMOD_META ) ) { + + controlDown = true; + } + + + // 26 is "SUB" which is ctrl-z on some platforms + // 24 is "CAN" which is ctrl-x on some platforms + // 23 is "ETB" which is ctrl-w on some platforms + // 5 is "ENQ" which is ctrl-e on some platforms + // 1 is "SOH" which is ctrl-a on some platforms + // but if alt-z or meta-z held, SUB won't be passed through + if( ( (mKeyA == 'z' ) && inKey == 26 ) || + ( (mKeyA == 'x' ) && inKey == 24 ) || + ( (mKeyA == 'w' ) && inKey == 23 ) || + ( (mKeyA == 'e' ) && inKey == 5 ) || + ( (mKeyA == 'a' ) && inKey == 1 ) || + ( controlDown && ( inKey == mKeyA || inKey == mKeyB ) ) ) { + + fireActionPerformed( this ); + } + } + + + + +UndoButtonGL::UndoButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : KeyEquivButtonGL( new Sprite( "undo.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight, + 'z', 'Z' ) { + setToolTip( "tip_undo" ); + } + + + + +RedoButtonGL::RedoButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "redo.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_redo" ); + } + + + + +FlipHButtonGL::FlipHButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "flipH.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_flipH" ); + } + + +FlipVButtonGL::FlipVButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "flipV.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_flipV" ); + } + + +RotateCCWButtonGL::RotateCCWButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "rotateCCW.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_rotateCCW" ); + } + +RotateCWButtonGL::RotateCWButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "rotateCW.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_rotateCW" ); + } + + + +ClearButtonGL::ClearButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "clear.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_clear" ); + } + + +ColorizeButtonGL::ColorizeButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( new Sprite( "colorize.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_colorize" ); + } + + + +void SelectableButtonGL::setSelected( char inSelected ) { + mSelected = inSelected; + } + +char SelectableButtonGL::getSelected() { + return mSelected; + } + + + +SelectableButtonGL::SelectableButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + // leave room for external border + : SpriteButtonGL( inSprite, inSpriteDrawScale, + inAnchorX + 2, inAnchorY + 2, + inWidth - 4, inHeight - 4 ), + mSelected( false ), + mOuterAnchorX( inAnchorX ), mOuterAnchorY( inAnchorY ), + mOuterWidth( inWidth ), mOuterHeight( inHeight ) { + + } + + + +// overrides from BorderButtonGL +void SelectableButtonGL::drawBorder() { + + + if( mSelected ) { + + // 1 pixel wide + glColor4f( mBorderColor.comp.r / 255.0f, + mBorderColor.comp.g / 255.0f, + mBorderColor.comp.b / 255.0f, + 1 ); + + glBegin( GL_QUADS ); { + glVertex2d( mOuterAnchorX, mOuterAnchorY ); + glVertex2d( mOuterAnchorX + mOuterWidth, mOuterAnchorY ); + glVertex2d( mOuterAnchorX + mOuterWidth, + mOuterAnchorY + mOuterHeight ); + glVertex2d( mOuterAnchorX, mOuterAnchorY + mOuterHeight ); + } + glEnd(); + + // fill + glColor4f( mFillColor.comp.r / 255.0f, + mFillColor.comp.g / 255.0f, + mFillColor.comp.b / 255.0f, + 1 ); + + glBegin( GL_QUADS ); { + glVertex2d( mOuterAnchorX + 1, mOuterAnchorY + 1 ); + glVertex2d( mOuterAnchorX + mOuterWidth - 1, mOuterAnchorY + 1 ); + glVertex2d( mOuterAnchorX + mOuterWidth - 1, + mOuterAnchorY + mOuterHeight - 1 ); + glVertex2d( mOuterAnchorX + 1 , mOuterAnchorY + mOuterHeight - 1 ); + } + glEnd(); + } + + // draw internal border + BorderButtonGL::drawBorder(); + } + + + + + +DoublePressSpriteButtonGL::DoublePressSpriteButtonGL( + Sprite *inSprite, Sprite *inConfirmSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( inSprite, inSpriteDrawScale, inAnchorX, inAnchorY, + inWidth, inHeight ), + mOtherSprite( inConfirmSprite ), + mPressedOnce( false ), + mFirstTip( NULL ), + mConfirmTip( NULL ) { + + } + + + +DoublePressSpriteButtonGL::~DoublePressSpriteButtonGL() { + delete mOtherSprite; + + if( mFirstTip != NULL ) { + delete [] mFirstTip; + } + if( mConfirmTip != NULL ) { + delete [] mConfirmTip; + } + } + + +/* +void DoublePressSpriteButtonGL::resetPressState() { + if( mPressedOnce ) { + + // swap sprites + Sprite *temp = mSprite; + mSprite = mOtherSprite; + mOtherSprite = temp; + + mPressedOnce = false; + + if( mTip != NULL ) { + delete [] mTip; + if( mFirstTip != NULL ) { + mTip = stringDuplicate( mFirstTip ); + } + } + } + } +*/ + + +void DoublePressSpriteButtonGL::setEnabled( char inEnabled ) { + SpriteButtonGL::setEnabled( inEnabled ); + + if( !inEnabled && mPressedOnce ) { + + // swap sprites + Sprite *temp = mSprite; + mSprite = mOtherSprite; + mOtherSprite = temp; + + mPressedOnce = false; + + if( mTip != NULL ) { + delete [] mTip; + if( mFirstTip != NULL ) { + mTip = stringDuplicate( mFirstTip ); + } + } + } + } + + + + +void DoublePressSpriteButtonGL::setSprite( Sprite *inSprite ) { + AppLog::warning( "unsupported setSprite called on " + "DoublePressSpriteButtonGL" ); + } + + +// overrides +void DoublePressSpriteButtonGL::setToolTip( const char *inTipTranslationKey ) { + ToolTipButtonGL::setToolTip( inTipTranslationKey ); + + const char *confirmSuffix = + TranslationManager::translate( "tip_confirm" ); + + if( mFirstTip != NULL ) { + delete [] mFirstTip; + mFirstTip = NULL; + } + if( mConfirmTip != NULL ) { + delete [] mConfirmTip; + mConfirmTip = NULL; + } + + if( mTip != NULL ) { + mFirstTip = stringDuplicate( mTip ); + mConfirmTip = autoSprintf( "%s%s", mTip, confirmSuffix ); + } + } + + +void DoublePressSpriteButtonGL::mouseReleased( double inX, double inY ) { + mPressed = false; + + if( isEnabled() && isInside( inX, inY ) ) { + if( mPressedOnce ) { + // second press! + mPressedOnce = false; + fireActionPerformed( this ); + } + else { + mPressedOnce = true; + // wait for second press + } + + // swap sprites + Sprite *temp = mSprite; + mSprite = mOtherSprite; + mOtherSprite = temp; + } + else { + // press outside + if( mPressedOnce ) { + // cancel + mPressedOnce = false; + + // swap sprites + Sprite *temp = mSprite; + mSprite = mOtherSprite; + mOtherSprite = temp; + } + } + + + if( mTip != NULL ) { + delete [] mTip; + + if( mPressedOnce && mConfirmTip != NULL ) { + mTip = stringDuplicate( mConfirmTip ); + } + else if( mFirstTip != NULL ) { + mTip = stringDuplicate( mFirstTip ); + } + if( isEnabled() && isInside( inX, inY ) ) { + ToolTipManager::setTip( mTip ); + } + if( !isEnabled() && isInside( inX, inY ) ) { + ToolTipManager::setTip( NULL ); + } + } + } + + + +DeleteButtonGL::DeleteButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : DoublePressSpriteButtonGL( new Sprite( "delete.tga", true ), + new Sprite( "confirm.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, + inWidth, inHeight ) { + } + + + +SendButtonGL::SendButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : DoublePressSpriteButtonGL( new Sprite( "send.tga", true ), + new Sprite( "sendConfirm.tga", true ), + inWidth / 16, + inAnchorX, inAnchorY, + inWidth, inHeight ) { + } + + + +ToggleSpriteButtonGL::ToggleSpriteButtonGL( + Sprite *inSprite, Sprite *inSecondSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( inSprite, inSpriteDrawScale, inAnchorX, inAnchorY, + inWidth, inHeight ), + mOtherSprite( inSecondSprite ), + mState( false ), + mFirstTip( NULL ), + mSecondTip( NULL ) { + + } + + + +ToggleSpriteButtonGL::~ToggleSpriteButtonGL() { + delete mOtherSprite; + if( mFirstTip != NULL ) { + delete [] mFirstTip; + } + if( mSecondTip != NULL ) { + delete [] mSecondTip; + } + } + + + +void ToggleSpriteButtonGL::setToolTip( const char *inTipTranslationKey ) { + ToolTipButtonGL::setToolTip( inTipTranslationKey ); + + if( mFirstTip != NULL ) { + delete [] mFirstTip; + mFirstTip = NULL; + } + + if( mTip != NULL ) { + mFirstTip = stringDuplicate( mTip ); + } + } + + +void ToggleSpriteButtonGL::setSecondToolTip( + const char *inTipTranslationKey ) { + + if( mSecondTip != NULL ) { + delete [] mSecondTip; + mSecondTip = NULL; + } + + if( inTipTranslationKey != NULL ) { + char *string = + (char *)TranslationManager::translate( + (char*)inTipTranslationKey ); + + mSecondTip = stringDuplicate( string ); + } + } + + + +void ToggleSpriteButtonGL::setSprite( Sprite *inSprite ) { + AppLog::warning( "unsupported setSprite called on " + "ToggleSpriteButtonGL" ); + } + + +void ToggleSpriteButtonGL::setState( char inState ) { + if( inState != mState ) { + + mState = !mState; + + // swap sprites + Sprite *temp = mSprite; + mSprite = mOtherSprite; + mOtherSprite = temp; + + + if( mTip != NULL ) { + delete [] mTip; + mTip = NULL; + + if( mState && mSecondTip != NULL ) { + mTip = stringDuplicate( mSecondTip ); + } + else if( mFirstTip != NULL ) { + mTip = stringDuplicate( mFirstTip ); + } + } + } + } + + + +// overrides +void ToggleSpriteButtonGL::mouseReleased( double inX, double inY ) { + if( isEnabled() && isInside( inX, inY ) && mPressStartedOnUs ) { + setState( !mState ); + + if( isEnabled() && isInside( inX, inY ) ) { + ToolTipManager::setTip( mTip ); + } + if( !isEnabled() && isInside( inX, inY ) ) { + ToolTipManager::setTip( NULL ); + } + } + + SpriteButtonGL::mouseReleased( inX, inY ); + } + + + +StackSearchButtonGL::StackSearchButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ToggleSpriteButtonGL( new Sprite( "stack.tga", true ), + new Sprite( "search.tga", true ), + inWidth / 8, + inAnchorX, inAnchorY, inWidth, inHeight ) { + setToolTip( "tip_stackMode" ); + setSecondToolTip( "tip_searchMode" ); + } + + + + + +SpriteCellButtonGL::SpriteCellButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ToolTipButtonGL( inAnchorX, inAnchorY, inWidth, inHeight ), + mSprite( inSprite ), + mBlankSprite( NULL ), + mSpriteDrawScale( inSpriteDrawScale ), + mHighlightOn( false ), + mColor( NULL ), mFireOnDrag( true ), mFireOnRelease( true ) { + + } + + + +SpriteCellButtonGL::~SpriteCellButtonGL() { + setSprite( NULL ); + + if( mColor != NULL ) { + delete mColor; + } + } + + +void SpriteCellButtonGL::setColor( Color *inColor ) { + if( mColor != NULL ) { + delete mColor; + } + mColor = inColor; + } + + +void SpriteCellButtonGL::setHighlight( char inHighlightOn ) { + mHighlightOn = inHighlightOn; + } + + + +void SpriteCellButtonGL::setSprite( Sprite *inSprite ) { + if( mSprite != NULL ) { + delete mSprite; + } + mSprite = inSprite; + } + + + +Sprite *SpriteCellButtonGL::getSprite() { + return mSprite; + } + + + +void SpriteCellButtonGL::setBlankSprite( Sprite *inSprite ) { + mBlankSprite = inSprite; + } + + +void SpriteCellButtonGL::drawPressed() { + // same as unpressed + drawUnpressed(); + } + + + +void SpriteCellButtonGL::drawUnpressed() { + if( mEnabled ) { + if( mSprite != NULL ) { + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + mSprite->draw( 0, 0, &pos, mSpriteDrawScale, 1, mColor ); + } + else if( mBlankSprite != NULL ) { + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + mBlankSprite->draw( 0, 0, &pos, mSpriteDrawScale ); + } + + if( mHighlightOn ) { + // draw highlight over top + glColor4f( 1, + 0, 0, 0.36f ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + // draw x to make sure highlight visible + glColor4f( 1, 1, 1, 0.5f ); + glBegin( GL_LINES ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + } + + } + } + + +void SpriteCellButtonGL::mousePressed( double inX, double inY ) { + ButtonGL::mousePressed( inX, inY ); + + if( isEnabled() ) { + // event + fireActionPerformed( this ); + } + } + + + +void SpriteCellButtonGL::mouseDragged( double inX, double inY ) { + ButtonGL::mouseDragged( inX, inY ); + + if( mFireOnDrag && isEnabled() && isInside( inX, inY ) ) { + // override pressed behavior, even if press didn't start on us + mPressed = true; + + // fire an event + fireActionPerformed( this ); + } + } + + + +void SpriteCellButtonGL::mouseReleased( double inX, double inY ) { + // always unpress on a release + mPressed = false; + + if( mFireOnRelease && isEnabled() && isInside( inX, inY ) ) { + // fire an event, even if press didn't start on us + fireActionPerformed( this ); + } + + // reset for next time + mPressStartedOnUs = false; + } + + + +#include "Song.h" +#include "musicPlayer.h" +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; + +extern int partLengths[PARTS]; +extern int partPositions[PARTS]; +extern int lastNoteColumnPlayed; + + +MusicCellButtonGL::MusicCellButtonGL( + Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteCellButtonGL( inSprite, inSpriteDrawScale, + inAnchorX, inAnchorY, + inWidth, inHeight ), + mTimeMark( "timeMark.tga", true ), + mPartNumber( 0 ), mPhraseNumber( 0 ) { + + // only fire on press and release + // to avoid firing when press didn't start here + mFireOnDrag = false; + } + + + +void MusicCellButtonGL::setMusicInfo( int inPartNumber, int inPhraseNumber ) { + mPartNumber = inPartNumber; + mPhraseNumber = inPhraseNumber; + } + + + +void MusicCellButtonGL::drawUnpressed() { + + + if( mEnabled ) { + if( mSprite != NULL ) { + + if( partPositions[ mPartNumber ] == mPhraseNumber ) { + + + Vector3D timePos( mAnchorX + lastNoteColumnPlayed + 1, + mAnchorY + mHeight / 2, 0 ); + + mTimeMark.draw( 0, 0, &timePos, mSpriteDrawScale ); + } + } + } + // draw notes on top + SpriteCellButtonGL::drawUnpressed(); + + // thin white border, partly transparent + glColor4f( 1, + 1, 1, 0.25f ); + + glBegin( GL_LINE_LOOP ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + } + + + +ScaleToggleButton::ScaleToggleButton( + Sprite *inSprite, Sprite *inSecondSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ToggleSpriteButtonGL( inSprite, inSecondSprite, + inSpriteDrawScale, + inAnchorX, inAnchorY, + inWidth, inHeight ), + mOnFade( 0 ) { + } + + + +void ScaleToggleButton::setMusicInfo( int inNoteNumber, + int inNotesPerOctave, + int inPartToWatch ) { + mNoteNumber = inNoteNumber; + mNotesPerOctave = inNotesPerOctave; + mPartToWatch = inPartToWatch; + } + + +Color *ScaleToggleButton::getDrawColor() { + char noteOn = false; + + if( mNotesPerOctave > 0 ) { + + for( int i=0; i 0 ) { + // gradual fade out after note hit + mOnFade -= 0.1; + if( mOnFade < 0 ) { + mOnFade = 0; + } + Color *cWhite = new Color( 1, 1, 1 ); + + c = Color::linearSum( cWhite, mColor, mOnFade ); + delete cWhite; + + } + + /* + else if( (noteOn && mOnFade < 1.0 ) || + (!noteOn && mOnFade > 0 ) ) { + if( noteOn ) { + // instant jump + mOnFade = 1.0; + } + else { + // gradual fade out + mOnFade -= 0.2; + if( mOnFade < 0 ) { + mOnFade = 0; + } + } + + Color *cWhite = new Color( 1, 1, 1 ); + + c = Color::linearSum( cWhite, mColor, mOnFade ); + } + */ + else { + // normal + c = mColor->copy(); + } + + return c; + } + + + +void ScaleToggleButton::drawUnpressed() { + Color *oldColor = mColor; + + mColor = getDrawColor(); + + ToggleSpriteButtonGL::drawUnpressed(); + + delete mColor; + mColor = oldColor; + } + + + +void ScaleToggleButton::drawPressed() { + Color *oldColor = mColor; + + mColor = getDrawColor(); + + ToggleSpriteButtonGL::drawPressed(); + + delete mColor; + mColor = oldColor; + } + + + + + + +HighlightColorButtonGL::HighlightColorButtonGL( + rgbaColor inColor, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : ColorButtonGL( inColor, inAnchorX, inAnchorY, + inWidth, inHeight ), + mHighlightOn( false ), + mSelectionOn( false ), + mOverlayOn( false ), + mOverlayTrans( false ) { + + } + + + +void HighlightColorButtonGL::setHighlight( char inHighlightOn ) { + mHighlightOn = inHighlightOn; + } + + + +void HighlightColorButtonGL::setSelection( char inSelectionOn ) { + mSelectionOn = inSelectionOn; + } + + +void HighlightColorButtonGL::setOverlay( char inOverlayOn ) { + mOverlayOn = inOverlayOn; + } + +void HighlightColorButtonGL::setOverlayColor( rgbaColor inColor ) { + mOverlayColor = inColor; + } + +void HighlightColorButtonGL::setOverlayTrans( char inTrans ) { + mOverlayTrans = inTrans; + } + + + +void HighlightColorButtonGL::drawPressed() { + // same as unpressed + drawUnpressed(); + } + +void HighlightColorButtonGL::drawCheckerboard( unsigned char inAlpha ) { + double halfWidth = (int)( mWidth / 2 ); + double halfHeight = (int)( mHeight / 2 ); + + glBegin( GL_QUADS ); { + /* + // darker border + glColor4f( 0.32f, 0.32f, 0.32f, inAlpha / 255.0f ); + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + */ + + // black inner + glColor4f( 0, 0, 0, inAlpha / 255.0f ); + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + + // gray checks on top + glColor4f( 0.1875f, 0.1875f, 0.1875f, 1 ); + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + halfWidth, mAnchorY ); + glVertex2d( mAnchorX + halfWidth, mAnchorY + halfHeight ); + glVertex2d( mAnchorX, mAnchorY + halfHeight ); + + glVertex2d( mAnchorX + halfWidth + 0.5, + mAnchorY + halfHeight + 0.5 ); + glVertex2d( mAnchorX + mWidth, + mAnchorY + halfHeight + 0.5 ); + glVertex2d( mAnchorX + mWidth, + mAnchorY + mHeight ); + glVertex2d( mAnchorX + halfWidth + 0.5, + mAnchorY + mHeight ); + } + glEnd(); + } + + + +void HighlightColorButtonGL::drawUnpressed() { + ColorButtonGL::drawUnpressed(); + + if( mEnabled ) { + + if( mHighlightOn ) { + // the term "highlight" is outdated, since it used to be used + // to show transparency mask pixels in the sprite editor + // now a checkerboard is used instead + + // draw checkerboard + drawCheckerboard( 255 ); + } + + + if( mOverlayOn && ! mOverlayTrans ) { + // draw color overlay over top + glColor4f( mOverlayColor.comp.r / 255.0f, + mOverlayColor.comp.g / 255.0f, + mOverlayColor.comp.b / 255.0f, 0.5f ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + // draw solid, internal box w/border to make sure overlay visible + double gap = 3; + + glColor4f( mOverlayColor.comp.r / 255.0f, + mOverlayColor.comp.g / 255.0f, + mOverlayColor.comp.b / 255.0f, 0.5f ); + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX + gap, mAnchorY + gap ); + glVertex2d( mAnchorX + mWidth - gap, mAnchorY + gap ); + glVertex2d( mAnchorX + mWidth - gap, + mAnchorY + mHeight - gap ); + glVertex2d( mAnchorX + gap, mAnchorY + mHeight - gap ); + } + glEnd(); + } + else if( mOverlayOn ) { + // draw trans checkerboard overlay + drawCheckerboard( 128 ); + } + + + + + + if( mSelectionOn ) { + // draw selection over top + glColor4f( 1, + 0, 0, 0.36f ); + + glBegin( GL_QUADS ); { + glVertex2d( mAnchorX, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY ); + glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight ); + glVertex2d( mAnchorX, mAnchorY + mHeight ); + } + glEnd(); + + // draw internal box to make sure selection visible + double gap = 3; + glColor4f( 0, 0, 0, 0.5f ); + glBegin( GL_LINE_LOOP ); { + glVertex2d( mAnchorX + gap, mAnchorY + gap ); + glVertex2d( mAnchorX + mWidth - gap, mAnchorY + gap ); + glVertex2d( mAnchorX + mWidth - gap, + mAnchorY + mHeight - gap ); + glVertex2d( mAnchorX + gap, mAnchorY + mHeight - gap ); + } + glEnd(); + + } + + } + } + + + + + +TwoSpriteButtonGL::TwoSpriteButtonGL( Sprite *inSprite, + Sprite *inFrontSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : SpriteButtonGL( inSprite, inSpriteDrawScale, + inAnchorX, inAnchorY, inWidth, inHeight ), + mFrontSprite( inFrontSprite ) { + } + + + +TwoSpriteButtonGL::~TwoSpriteButtonGL() { + setFrontSprite( NULL ); + } + + + +void TwoSpriteButtonGL::setFrontSprite( Sprite *inSprite ) { + if( mFrontSprite != NULL ) { + delete mFrontSprite; + } + mFrontSprite = inSprite; + } + + +void TwoSpriteButtonGL::drawPressed() { + SpriteButtonGL::drawPressed(); + + if( mEnabled ) { + if( mFrontSprite != NULL ) { + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + // a bit darker when pressed + Color c( .75, .75, .75, 1 ); + + mFrontSprite->draw( 0, 0, &pos, mSpriteDrawScale, 1, &c ); + } + } + } + + + +void TwoSpriteButtonGL::drawUnpressed() { + SpriteButtonGL::drawUnpressed(); + + if( mEnabled ) { + if( mFrontSprite != NULL ) { + + Vector3D pos( mAnchorX + mWidth / 2, mAnchorY + mHeight/2, 0 ); + + mFrontSprite->draw( 0, 0, &pos, mSpriteDrawScale ); + } + } + } + + + +DraggableTwoSpriteButtonGL::DraggableTwoSpriteButtonGL( + Sprite *inSprite, + Sprite *inFrontSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ) + : TwoSpriteButtonGL( inSprite, inFrontSprite, inSpriteDrawScale, + inAnchorX, inAnchorY, inWidth, inHeight ), + mLastActionFromPress( false ) { + + } + + + +void DraggableTwoSpriteButtonGL::mousePressed( double inX, double inY ) { + TwoSpriteButtonGL::mousePressed( inX, inY ); + + if( isEnabled() ) { + mLastActionFromPress = true; + fireActionPerformed( this ); + } + } + + +void DraggableTwoSpriteButtonGL::mouseReleased( double inX, double inY ) { + mLastActionFromPress = false; + + TwoSpriteButtonGL::mouseReleased( inX, inY ); + } diff --git a/gameSource/buttons.h b/gameSource/buttons.h new file mode 100644 index 0000000..a0b5477 --- /dev/null +++ b/gameSource/buttons.h @@ -0,0 +1,697 @@ +#ifndef BUTTONS_INCLUDED +#define BUTTONS_INCLUDED + + +#include "minorGems/graphics/openGL/gui/ButtonGL.h" + +#include "color.h" + + + + +class ToolTipButtonGL : public ButtonGL { + public: + ToolTipButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual ~ToolTipButtonGL(); + + // defaults to no tip + // automatically invokes TranslationManager on passed-in key + // if inUseTranslationManager is false, uses Key as tool tip directly + virtual void setToolTip( const char *inTipTranslationKey, + char inUseTranslationManager = true ); + + + // override functions in ButtonGL + virtual void mouseMoved( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + protected: + char *mTip; + }; + + + + + +class BorderButtonGL : public ToolTipButtonGL { + public: + void setFillColor( rgbaColor inColor ); + + void setBorderColor( rgbaColor inColor ); + + protected: + BorderButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + + virtual void drawBorder(); + + rgbaColor mBorderColor; + rgbaColor mFillColor; + + }; + + + +// base class for buttons with icons +class IconButtonGL : public BorderButtonGL { + + public: + IconButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + protected: + // subclasses must implement this + virtual void drawIcon() = 0; + }; + + + + +class AddButtonGL : public IconButtonGL { + + public: + AddButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + protected: + + void drawIcon(); + }; + + + + + + + + + +#include "Sprite.h" + + +// buttons that display sprites inside a border +// sprite can be NULL +// invisible when disabled +class SpriteButtonGL : public BorderButtonGL { + + public: + // sprite is destroyed by this class + SpriteButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual ~SpriteButtonGL(); + + // defaults to white, destroyed internally + // set to NULL to restore to white + void setColor( Color *inColor ); + + + // replaces sprite + virtual void setSprite( Sprite *inSprite ); + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + protected: + Sprite *mSprite; + double mSpriteDrawScale; + + Color *mColor; + + }; + + + + +class LeftButtonGL : public SpriteButtonGL { + + public: + LeftButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + +class RightButtonGL : public SpriteButtonGL { + + public: + RightButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + +class EditButtonGL : public SpriteButtonGL { + + public: + EditButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + + +class SmallAddButtonGL : public SpriteButtonGL { + + public: + SmallAddButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + +// delete button with no confirmation behavior +class QuickDeleteButtonGL : public SpriteButtonGL { + + public: + QuickDeleteButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + +// a button with a keyboard equivalent (when CTRL held) +class KeyEquivButtonGL : public SpriteButtonGL { + + public: + + KeyEquivButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight, + char inKeyA, char inKeyB ); + + // always focused, listens for ctrl-z + virtual char isFocused(); + virtual void keyPressed( unsigned char inKey, double inX, double inY ); + protected: + char mKeyA, mKeyB; + }; + + + + +class CloseButtonGL : public KeyEquivButtonGL { + + public: + CloseButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + // override this to make click area slightly bigger + // (so it's infinite if this button in corner of screen) + virtual char isInside( double inX, double inY ); + + }; + + + +class NoKeyCloseButtonGL : public SpriteButtonGL { + + public: + NoKeyCloseButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + // override this to make click area slightly bigger + // (so it's infinite if this button in corner of screen) + virtual char isInside( double inX, double inY ); + + }; + + + + +class UndoButtonGL : public KeyEquivButtonGL { + + public: + UndoButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + }; + + + +class RedoButtonGL : public SpriteButtonGL { + + public: + RedoButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + +class FlipHButtonGL : public SpriteButtonGL { + + public: + FlipHButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + +class FlipVButtonGL : public SpriteButtonGL { + + public: + FlipVButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + +class RotateCCWButtonGL : public SpriteButtonGL { + + public: + RotateCCWButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + +class RotateCWButtonGL : public SpriteButtonGL { + + public: + RotateCWButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + +class ClearButtonGL : public SpriteButtonGL { + + public: + ClearButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + +class ColorizeButtonGL : public SpriteButtonGL { + + public: + ColorizeButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + + +// buttons that shows a color +// responds to dragging across it and pressing (as well as releasing) +// by firing events +class ColorButtonGL : public BorderButtonGL { + + public: + ColorButtonGL( rgbaColor inColor, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + // replaces color + void setColor( rgbaColor inColor, char inForceDarkBorder = false ); + + // was last action triggered by hover w/out press or release + char wasLastActionHover(); + + + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + + // overrides these + virtual void mousePressed( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseMoved( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + + + protected: + rgbaColor mColor; + + char mLastActionHover; + }; + + + + +// button that has a highlight that can be toggled +class HighlightColorButtonGL : public ColorButtonGL { + public: + HighlightColorButtonGL( rgbaColor inColor, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual void setHighlight( char inHighlightOn ); + + virtual void setSelection( char inSelectionOn ); + + virtual void setOverlay( char inOverlayOn ); + + virtual void setOverlayColor( rgbaColor inColor ); + + // does overlay represent a transparent area + virtual void setOverlayTrans( char inTrans ); + + // override + virtual void drawPressed(); + + virtual void drawUnpressed(); + + protected: + char mHighlightOn; + + char mSelectionOn; + + char mOverlayOn; + char mOverlayTrans; + + rgbaColor mOverlayColor; + + + void drawCheckerboard( unsigned char inAlpha ); + + + }; + + + + +// buttons that display sprites without a border +// sprite can be NULL +// invisible when disabled +// responds to dragging across it and pressing (as well as releasing) +// by firing events +class SpriteCellButtonGL : public ToolTipButtonGL { + + public: + // sprite is destroyed by this class + SpriteCellButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + ~SpriteCellButtonGL(); + + virtual void setHighlight( char inHighlightOn ); + + + // replaces sprite + // destroyed by this class + virtual void setSprite( Sprite *inSprite ); + + virtual Sprite *getSprite(); + + + // sets the sprite to draw when main sprite is NULL + // defaults to NULL (draw nothing) + // NOT destroyed by this class + virtual void setBlankSprite( Sprite *inSprite ); + + + // defaults to white, destroyed internally + // set to NULL to restore to white + void setColor( Color *inColor ); + + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + // overrides these + virtual void mousePressed( double inX, double inY ); + virtual void mouseDragged( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + + protected: + Sprite *mSprite; + Sprite *mBlankSprite; + double mSpriteDrawScale; + + char mHighlightOn; + + Color *mColor; + + // on by default... subclasses can turn off + char mFireOnDrag; + char mFireOnRelease; + }; + + + +// a SpriteCellButtonGL that also has a time position overlay, +// only displayed when sprite not NULL +class MusicCellButtonGL : public SpriteCellButtonGL { + + public: + // sprite is destroyed by this class + MusicCellButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + void setMusicInfo( int inPartNumber, int inPhraseNumber ); + + // override to draw timeline + void drawUnpressed(); + + + protected: + Sprite mTimeMark; + int mPartNumber, mPhraseNumber; + }; + + + + + + + +// a button that has a double-border when selected +// make sure sizing makes room for two borders +class SelectableButtonGL : public SpriteButtonGL { + public: + SelectableButtonGL( Sprite *inSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + void setSelected( char inSelected ); + char getSelected(); + + + + protected: + + // overrides from BorderButtonGL + virtual void drawBorder(); + + private: + + char mSelected; + + double mOuterAnchorX, mOuterAnchorY, mOuterWidth, mOuterHeight; + }; + + + + +// button that must be pressed twice before firing +// confirmation +class DoublePressSpriteButtonGL : public SpriteButtonGL { + + public: + // sprite is destroyed by this class + DoublePressSpriteButtonGL( Sprite *inSprite, Sprite *inConfirmSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + ~DoublePressSpriteButtonGL(); + + // resets button back to first, unpressed state + //void resetPressState(); + + // override to unpress when disabled + void setEnabled( char inEnabled ); + + + // overrides to PREVENT sprite replacement + virtual void setSprite( Sprite *inSprite ); + + // overrides + virtual void setToolTip( const char *inTipTranslationKey ); + virtual void mouseReleased( double inX, double inY ); + + protected: + Sprite *mOtherSprite; + + char mPressedOnce; + + char *mFirstTip; + char *mConfirmTip; + + }; + + + + +class DeleteButtonGL : public DoublePressSpriteButtonGL { + + public: + DeleteButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + }; + + + +class SendButtonGL : public DoublePressSpriteButtonGL { + + public: + SendButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + }; + + + + +// button that that has two states and two sprites (toggles when pressed) +class ToggleSpriteButtonGL : public SpriteButtonGL { + + public: + // sprite is destroyed by this class + ToggleSpriteButtonGL( Sprite *inSprite, Sprite *inSecondSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + ~ToggleSpriteButtonGL(); + + char getState() { + return mState; + } + + void setState( char inState ); + + + virtual void setSecondToolTip( const char *inTipTranslationKey ); + + // overrides to PREVENT sprite replacement + virtual void setSprite( Sprite *inSprite ); + + // overrides + virtual void setToolTip( const char *inTipTranslationKey ); + virtual void mouseReleased( double inX, double inY ); + + protected: + Sprite *mOtherSprite; + + char mState; + + char *mFirstTip; + char *mSecondTip; + }; + + + + +// a ToggleSpriteButtonGL that becomes brighter when it's scale note is +// being played +class ScaleToggleButton : public ToggleSpriteButtonGL { + + public: + ScaleToggleButton( Sprite *inSprite, Sprite *inSecondSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + void setMusicInfo( int inNoteNumber, int inNotesPerOctave, + int inPartToWatch ); + + // override to change draw color + void drawUnpressed(); + void drawPressed(); + + + protected: + Color *getDrawColor(); + + int mNoteNumber, mNotesPerOctave, mPartToWatch; + double mOnFade; + int mLastColumnOn; + }; + + + + + +class StackSearchButtonGL : public ToggleSpriteButtonGL { + + public: + StackSearchButtonGL( double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + }; + + + + + +// buttons that display TWO layered sprites inside a border +// sprites can be NULL +// invisible when disabled +class TwoSpriteButtonGL : public SpriteButtonGL { + + public: + // sprites are destroyed by this class + TwoSpriteButtonGL( Sprite *inSprite, + Sprite *inFrontSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + virtual ~TwoSpriteButtonGL(); + + // replaces sprite + virtual void setFrontSprite( Sprite *inSprite ); + + virtual void drawPressed(); + + virtual void drawUnpressed(); + + protected: + Sprite *mFrontSprite; + + }; + + + + +// version of two-sprite button that fires action upon press too, to +// potentially initiate drag and drop functionality +class DraggableTwoSpriteButtonGL : public TwoSpriteButtonGL { + + public: + // sprites are destroyed by this class + DraggableTwoSpriteButtonGL( Sprite *inSprite, + Sprite *inFrontSprite, + double inSpriteDrawScale, + double inAnchorX, double inAnchorY, + double inWidth, double inHeight ); + + // override functions in ButtonGL + virtual void mousePressed( double inX, double inY ); + virtual void mouseReleased( double inX, double inY ); + + char mLastActionFromPress; + }; + + + + + +#endif diff --git a/gameSource/canDrop.png b/gameSource/canDrop.png new file mode 100644 index 0000000..25b3eb6 Binary files /dev/null and b/gameSource/canDrop.png differ diff --git a/gameSource/clear.png b/gameSource/clear.png new file mode 100644 index 0000000..9008d7a Binary files /dev/null and b/gameSource/clear.png differ diff --git a/gameSource/close.png b/gameSource/close.png new file mode 100644 index 0000000..9cd1901 Binary files /dev/null and b/gameSource/close.png differ diff --git a/gameSource/color.h b/gameSource/color.h new file mode 100644 index 0000000..ea8f6e0 --- /dev/null +++ b/gameSource/color.h @@ -0,0 +1,70 @@ +#ifndef RGBA_COLOR_INCLUDED +#define RGBA_COLOR_INCLUDED + + +#include "minorGems/graphics/Color.h" + + +typedef union rgbaColor { + struct comp { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } comp; + + // access those bytes as an array + unsigned char bytes[4]; + + // reinterpret those bytes as an unsigned int + unsigned int rgbaInt; + } rgbaColor; + + + +inline char equal( rgbaColor inA, rgbaColor inB ) { + return inA.rgbaInt == inB.rgbaInt; + } + + +inline rgbaColor blend( rgbaColor inA, rgbaColor inB, double inBWeight ) { + rgbaColor blendC = + { + { (unsigned char)( + inA.comp.r * (1-inBWeight) + inB.comp.r * inBWeight ), + (unsigned char)( + inA.comp.g * (1-inBWeight) + inB.comp.g * inBWeight ), + (unsigned char)( + inA.comp.b * (1-inBWeight) + inB.comp.b * inBWeight ), + (unsigned char)( + inA.comp.a * (1-inBWeight) + inB.comp.a * inBWeight ) + } + }; + return blendC; + } + + + +inline Color toColor( rgbaColor inC ) { + Color c( inC.comp.r / 225.0f, + inC.comp.g / 225.0f, + inC.comp.b / 225.0f, + inC.comp.a / 225.0f ); + return c; + } + + + + + + + +#include + +inline void print( rgbaColor inC ) { + printf( "[%02x,%02x,%02x,%02x]", + inC.comp.r, inC.comp.g, inC.comp.b, inC.comp.a ); + } + + +#endif diff --git a/gameSource/colorSpot.png b/gameSource/colorSpot.png new file mode 100644 index 0000000..b717885 Binary files /dev/null and b/gameSource/colorSpot.png differ diff --git a/gameSource/colorize.png b/gameSource/colorize.png new file mode 100644 index 0000000..c62f832 Binary files /dev/null and b/gameSource/colorize.png differ diff --git a/gameSource/common.cpp b/gameSource/common.cpp new file mode 100644 index 0000000..eee5bd5 --- /dev/null +++ b/gameSource/common.cpp @@ -0,0 +1,176 @@ +#include "common.h" + +#include "minorGems/graphics/converters/TGAImageConverter.h" + +#include "minorGems/io/file/File.h" + +#include "minorGems/io/file/FileInputStream.h" + +#include "minorGems/util/log/AppLog.h" + + +#include +#include + + + +Image *readTGA( const char *inFileName ) { + return readTGA( "graphics", inFileName ); + } + + + +Image *readTGA( const char *inFolderName, const char *inFileName ) { + File tgaFile( new Path( inFolderName ), inFileName ); + FileInputStream tgaStream( &tgaFile ); + + TGAImageConverter converter; + + Image *result = converter.deformatImage( &tgaStream ); + + if( result == NULL ) { + char *logString = autoSprintf( + "CRITICAL ERROR: could not read TGA file %s, wrong format?", + inFileName ); + AppLog::criticalError( logString ); + delete [] logString; + } + return result; + } + + +void writeTGA( Image *inImage, char *inFileName ) { + File tgaFile( NULL, inFileName ); + FileOutputStream tgaStream( &tgaFile ); + + TGAImageConverter converter; + + return converter.formatImage( inImage, &tgaStream ); + } + + + + +double smoothBlend( double inValue ) { + + + return 1 - ( sin( inValue * M_PI + + + M_PI / 2 ) + + 1 ) / 2; + } + + +char equals( intPair inA, intPair inB ) { + return inA.x == inB.x && inA.y == inB.y; + } + + + +intPair add( intPair inA, intPair inB ) { + intPair returnVal; + returnVal.x = inA.x + inB.x; + returnVal.y = inA.y + inB.y; + + return returnVal; + } + + + +intPair subtract( intPair inA, intPair inB ) { + intPair returnVal; + returnVal.x = inA.x - inB.x; + returnVal.y = inA.y - inB.y; + + return returnVal; + } + + +void print( intPair inA ) { + printf( "{%d,%d}", inA.x, inA.y ); + } + + + +static unsigned char pairCharBuffer[8]; + +unsigned char *getChars( intPair inA ) { + + pairCharBuffer[0] = ( inA.x >> 24 ) & 0xFF; + pairCharBuffer[1] = ( inA.x >> 16 ) & 0xFF; + pairCharBuffer[2] = ( inA.x >> 8 ) & 0xFF; + pairCharBuffer[3] = ( inA.x ) & 0xFF; + + pairCharBuffer[4] = ( inA.y >> 24 ) & 0xFF; + pairCharBuffer[5] = ( inA.y >> 16 ) & 0xFF; + pairCharBuffer[6] = ( inA.y >> 8 ) & 0xFF; + pairCharBuffer[7] = ( inA.y ) & 0xFF; + + return pairCharBuffer; + } + + +intPair readIntPair( unsigned char *inBytes, int inLength, + int *outNumUsed ) { + + intPair p; + + if( inLength >= 8 ) { + p.x = + inBytes[0] << 24 | + inBytes[1] << 16 | + inBytes[2] << 8 | + inBytes[3]; + p.y = + inBytes[4] << 24 | + inBytes[5] << 16 | + inBytes[6] << 8 | + inBytes[7]; + + *outNumUsed = 8; + } + else { + AppLog::error( + "ERROR: not enough bytes in data string for an intPair" ); + *outNumUsed = -1; + } + + return p; + } + + + +unsigned char *getChars( int inA ) { + + pairCharBuffer[0] = ( inA >> 24 ) & 0xFF; + pairCharBuffer[1] = ( inA >> 16 ) & 0xFF; + pairCharBuffer[2] = ( inA >> 8 ) & 0xFF; + pairCharBuffer[3] = ( inA ) & 0xFF; + + return pairCharBuffer; + } + + +int readInt( unsigned char *inBytes, int inLength, + int *outNumUsed ) { + + int a; + + if( inLength >= 4 ) { + a = + inBytes[0] << 24 | + inBytes[1] << 16 | + inBytes[2] << 8 | + inBytes[3]; + + *outNumUsed = 4; + } + else { + AppLog::error( + "ERROR: not enough bytes in data string for an int" ); + *outNumUsed = -1; + } + + return a; + } + diff --git a/gameSource/common.h b/gameSource/common.h new file mode 100644 index 0000000..28959e6 --- /dev/null +++ b/gameSource/common.h @@ -0,0 +1,76 @@ +#ifndef COMMON_INCLUDED +#define COMMON_INCLUDED + + +// width of a grid square +#define P 16 + +// number of grid squares on screen is GxG (square) +#define G 13 + +// number of wells in a color palette +#define C 40 + + +// undo stack sizes +#define MAX_UNDOS 256 + + +// code used at the start of some resource files as an identifier +#define SID_MAGIC_CODE "SiD1977" + + + +#include "minorGems/graphics/Image.h" + +//#include + +// reads a TGA file from the default ("graphics") folder +Image *readTGA( const char *inFileName ); + + +Image *readTGA( const char *inFolderName, const char *inFileName ); + + +// write to the current directory +void writeTGA( Image *inImage, char *inFileName ); + + + +// maps linear values in [0,1] to smoothed [0,1] using sine +double smoothBlend( double inValue ); + + + +typedef struct intPair { + int x; + int y; + } intPair; + + +char equals( intPair inA, intPair inB ); + +intPair add( intPair inA, intPair inB ); + +intPair subtract( intPair inA, intPair inB ); + + +void print( intPair inA ); + + +// generates a bytes string representing a pair +// result is statically-allocated and overwritten with each subsequent call +// (NOT thread safe) +unsigned char *getChars( intPair inA ); + +// reads pair from data string +intPair readIntPair( unsigned char *inBytes, int inLength, + int *outNumUsed ); + + +unsigned char *getChars( int inA ); + +int readInt( unsigned char *inBytes, int inLength, int *outNumUsed ); + + +#endif diff --git a/gameSource/confirm.png b/gameSource/confirm.png new file mode 100644 index 0000000..c36b4a6 Binary files /dev/null and b/gameSource/confirm.png differ diff --git a/gameSource/delete.png b/gameSource/delete.png new file mode 100644 index 0000000..c9558f5 Binary files /dev/null and b/gameSource/delete.png differ diff --git a/gameSource/edit.png b/gameSource/edit.png new file mode 100644 index 0000000..bd701ab Binary files /dev/null and b/gameSource/edit.png differ diff --git a/gameSource/editObject.png b/gameSource/editObject.png new file mode 100644 index 0000000..e68165e Binary files /dev/null and b/gameSource/editObject.png differ diff --git a/gameSource/editRoom.png b/gameSource/editRoom.png new file mode 100644 index 0000000..47ff30d Binary files /dev/null and b/gameSource/editRoom.png differ diff --git a/gameSource/editScale.png b/gameSource/editScale.png new file mode 100644 index 0000000..3c4bf9e Binary files /dev/null and b/gameSource/editScale.png differ diff --git a/gameSource/editSprite.png b/gameSource/editSprite.png new file mode 100644 index 0000000..df3e5bd Binary files /dev/null and b/gameSource/editSprite.png differ diff --git a/gameSource/emptyPhrase.png b/gameSource/emptyPhrase.png new file mode 100644 index 0000000..1702101 Binary files /dev/null and b/gameSource/emptyPhrase.png differ diff --git a/gameSource/erase.png b/gameSource/erase.png new file mode 100644 index 0000000..3e203f3 Binary files /dev/null and b/gameSource/erase.png differ diff --git a/gameSource/fast.png b/gameSource/fast.png new file mode 100644 index 0000000..86d1655 Binary files /dev/null and b/gameSource/fast.png differ diff --git a/gameSource/fill.png b/gameSource/fill.png new file mode 100644 index 0000000..02cc302 Binary files /dev/null and b/gameSource/fill.png differ diff --git a/gameSource/first.png b/gameSource/first.png new file mode 100644 index 0000000..144457d Binary files /dev/null and b/gameSource/first.png differ diff --git a/gameSource/fixOldResources.cpp b/gameSource/fixOldResources.cpp new file mode 100644 index 0000000..4e067f0 --- /dev/null +++ b/gameSource/fixOldResources.cpp @@ -0,0 +1,152 @@ + + +#include "fixOldResources.h" +#include "resourceDatabase.h" +#include "resourceManager.h" +#include "Scene.h" +#include "StateObject.h" + +SimpleVector< uniqueID > objectsToDelete; +SimpleVector< uniqueID > scenesToDelete; + + + +void fixOldResources( uniqueID *inScenes, int inNumScenes, + uniqueID *inObjects, int inNumObjects ) { + + int numScenes; + uniqueID *scenes; + + + if( inScenes != NULL ) { + numScenes = inNumScenes; + scenes = inScenes; + } + else { + + numScenes = countSearchResults( "scene", "" ); + scenes = new uniqueID[ numScenes ]; + + int numGotten = getSearchResults( "scene", "", 0, numScenes, scenes ); + + numScenes = numGotten; + } + + printf( "processing %d scenes\n", numScenes ); + + + for( int i=0; i + + +// fixes old v13 resources, replacing them with v14 resources +// (to avoid cache duplicates) + +// if none specified, all in cache are processed +void fixOldResources( uniqueID *inScenes = NULL, int inNumScenes = -1, + uniqueID *inObjects = NULL, int inNumObjects = -1 ); diff --git a/gameSource/flipBook.png b/gameSource/flipBook.png new file mode 100644 index 0000000..2ed977e Binary files /dev/null and b/gameSource/flipBook.png differ diff --git a/gameSource/flipH.png b/gameSource/flipH.png new file mode 100644 index 0000000..f431313 Binary files /dev/null and b/gameSource/flipH.png differ diff --git a/gameSource/flipV.png b/gameSource/flipV.png new file mode 100644 index 0000000..c460353 Binary files /dev/null and b/gameSource/flipV.png differ diff --git a/gameSource/font_8_16.png b/gameSource/font_8_16.png new file mode 100644 index 0000000..0af3049 Binary files /dev/null and b/gameSource/font_8_16.png differ diff --git a/gameSource/freeze.png b/gameSource/freeze.png new file mode 100644 index 0000000..daff4ae Binary files /dev/null and b/gameSource/freeze.png differ diff --git a/gameSource/game.cpp b/gameSource/game.cpp new file mode 100644 index 0000000..d5dfef1 --- /dev/null +++ b/gameSource/game.cpp @@ -0,0 +1,2265 @@ +/* + * Modification History + * + * 2008-September-11 Jason Rohrer + * Created. Copied from Cultivation. + */ + + +#include +#include +#include +#include +#include + + +// let SDL override our main function with SDLMain +#include + + +// must do this before SDL include to prevent WinMain linker errors on win32 +int mainFunction( int inArgCount, char **inArgs ); + +int main( int inArgCount, char **inArgs ) { + return mainFunction( inArgCount, inArgs ); + } + + +#include + + + +#include "minorGems/graphics/openGL/ScreenGL.h" +#include "minorGems/graphics/openGL/SceneHandlerGL.h" +#include "minorGems/graphics/Color.h" + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/graphics/openGL/gui/GUITranslatorGL.h" +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/graphics/openGL/gui/LabelGL.h" +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" +#include "minorGems/graphics/openGL/gui/SliderGL.h" + + + +#include "minorGems/system/Time.h" +#include "minorGems/system/Thread.h" + +#include "minorGems/io/file/File.h" + +#include "minorGems/network/HostAddress.h" + +#include "minorGems/network/upnp/portMapping.h" + + +#include "minorGems/util/SettingsManager.h" +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" + + +#include "minorGems/util/log/AppLog.h" +#include "minorGems/util/log/FileLog.h" + + +#include "common.h" +#include "HighlightLabelGL.h" +#include "Connection.h" +#include "GameHalf.h" +#include "PlayerGame.h" +#include "ControllerGame.h" +#include "musicPlayer.h" +#include "Song.h" +#include "DemoCodeChecker.h" +#include "resourceManager.h" + + + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; +extern int partLengths[PARTS]; + + + +// some settings + +// size of game image +int gameWidth = 320; +int gameHeight = 240; + +// size of screen for fullscreen mode +int screenWidth = 640; +int screenHeight = 480; + + +char hardToQuitMode = false; + + +double musicTrackFadeLevel = 1.0; + + +char autoJoinMode = false; +char autoHostMode = false; + + +char *autoJoinAddress = NULL; +int autoJoinPort = -1; + + +// ^ and & keys to slow down and speed up for testing +char enableSlowdownKeys = false; +//char enableSlowdownKeys = true; + + +// is demo mode on? If so, it checks server to see if it is still allowed +// to run. +char demoMode = false; + +int demoCodeLength = 10; + +DemoCodeChecker *codeChecker = NULL; + + +// track old code checkers so that we can destroy them after they unblock +SimpleVector oldCodeCheckers; + + + + +// these are shared by other modules as extern +Connection *connection = NULL; +TextGL *largeText = NULL; +TextGL *largeTextFixed = NULL; +char *addressString = NULL; + + +// should we try UPNP search (blocks) on next redraw? +char tryUPNP = false; +char tryUPNPClose = false; +char upnpPortOpen = false; + + +// track old clients so that we can destroy them after they unblock +SimpleVector oldConnections; + + +GameHalf *game = NULL; +char isControllerGame = false; +char loadingDrawnOnce = false; + + + +class GameSceneHandler : + public SceneHandlerGL, public MouseHandlerGL, public KeyboardHandlerGL, + public RedrawListenerGL, public ActionListener { + + public: + + /** + * Constructs a sceen handler. + * + * @param inScreen the screen to interact with. + * Must be destroyed by caller after this class is destroyed. + */ + GameSceneHandler( ScreenGL *inScreen ); + + virtual ~GameSceneHandler(); + + + + /** + * Executes necessary init code that reads from files. + * + * Must be called before using a newly-constructed GameSceneHandler. + * + * This call assumes that the needed files are in the current working + * directory. + */ + void initFromFiles(); + + + + ScreenGL *mScreen; + + + + + + + + + // implements the SceneHandlerGL interface + virtual void drawScene(); + + // implements the MouseHandlerGL interface + virtual void mouseMoved( int inX, int inY ); + virtual void mouseDragged( int inX, int inY ); + virtual void mousePressed( int inX, int inY ); + virtual void mouseReleased( int inX, int inY ); + + // implements the KeyboardHandlerGL interface + virtual char isFocused() { + // always focused + return true; + } + virtual void keyPressed( unsigned char inKey, int inX, int inY ); + virtual void specialKeyPressed( int inKey, int inX, int inY ); + virtual void keyReleased( unsigned char inKey, int inX, int inY ); + virtual void specialKeyReleased( int inKey, int inX, int inY ); + + // implements the RedrawListener interface + virtual void fireRedraw(); + + + // implements the ActionListener interface + virtual void actionPerformed( GUIComponent *inTarget ); + + + + protected: + + // sets the string on a label and re-centers it + void setLabelString( LabelGL *inLabel, + const char *inTranslationString, + double inScaleFactor = 1.0 ); + + // creates a centerd label at a particular height + HighlightLabelGL *createLabel( double inGuiY, + const char *inTranslationString, + TextGL *inText = NULL ); + + void processSelection( int inSelection ); + + int mStartTimeSeconds; + + char mPaused; + + + char mPrintFrameRate; + unsigned long mNumFrames; + unsigned long mFrameBatchSize; + unsigned long mFrameBatchStartTimeSeconds; + unsigned long mFrameBatchStartTimeMilliseconds; + + + + Color mBackgroundColor; + + + + + // for game selection display + char mGUIVisible; + + GUIPanelGL *mMainPanel; + GUITranslatorGL *mMainPanelGuiTranslator; + + TextGL *mTextGL; + TextGL *mTextGLFixedWidth; + + GUIPanelGL *mCurrentPanel; + + + GUIPanelGL *mWriteFailedPanel; + HighlightLabelGL *mWriteFailedLabel; + + + + GUIPanelGL *mDemoCodePanel; + HighlightLabelGL *mEnterDemoCodeLabel; + TextFieldGL *mEnterDemoCodeField; + + + + + GUIPanelGL *mTitlePanel; + HighlightLabelGL *mTitleLabel; + + + GUIPanelGL *mVolumePanel; + HighlightLabelGL *mVolumeLabel; + HighlightLabelGL *mVolumeDoneLabel; + + SliderGL *mVolumeSlider; + + // avoid abrupt volume changes that cause clicks + double mVolumeTarget; + + + + GUIPanelGL *mSelectPanel; + HighlightLabelGL *mControlLocalLabel; + HighlightLabelGL *mControlRemoteLabel; + HighlightLabelGL *mJoinAsPlayerLabel; + + // track as array to make toggling easier + HighlightLabelGL *mSelectLabels[3]; + int mSelectHighlightIndex; + + + GUIPanelGL *mWaitPlayerPanel; + HighlightLabelGL *mWaitPlayerMessageLabel; + HighlightLabelGL *mWaitPlayerLabel; + HighlightLabelGL *mAddressLabel; + HighlightLabelGL *mDoNotWaitTipLabel; + HighlightLabelGL *mPortTipLabel; + + + GUIPanelGL *mUPNPPanel; + HighlightLabelGL *mUPNPLabel; + + + GUIPanelGL *mJoinControllerPanel; + HighlightLabelGL *mEnterAddressLabel; + TextFieldGL *mEnterAddressField; + + + + GUIPanelGL *mLoadingPanel; + HighlightLabelGL *mLoadingLabel; + + }; + + + +GameSceneHandler *sceneHandler; +ScreenGL *screen; +GUITranslatorGL *guiTranslator; + + +// how many pixels wide is each game pixel drawn as? +int pixelZoomFactor; + + + +int maxAddressLength = 15; + + + + +// function that destroys object when exit is called. +// exit is the only way to stop the loop in ScreenGL +void cleanUpAtExit() { + AppLog::info( "exiting\n" ); + + + if( game != NULL ) { + delete game; + } + + + delete sceneHandler; + delete screen; + + + if( codeChecker != NULL ) { + delete codeChecker; + } + + for( int i=0; i 1 ) { + + autoJoinAddress = stringDuplicate( inArgs[1] ); + + char *colonPointer = strstr( autoJoinAddress, ":" ); + + if( colonPointer == NULL ) { + // address, no port + // don't touch the string + } + else { + sscanf( &( colonPointer[1] ), "%d", &autoJoinPort ); + + // terminate + colonPointer[0] = '\0'; + } + } + + + + + /* + // test tree + StringTree *tree = new StringTree(); + + // use strings as values, too + char *apple = "apple"; + tree->insert( "apple", (void*)apple ); + //tree->insert( "dapper", (void*)"dapper" ); + //tree->insert( "relapse", (void*)"relapse" ); + tree->insert( "apron", (void*)"apron" ); + + char* aaron = "aaron"; + + tree->insert( "aaron", (void*)aaron ); + + tree->insert( "no", (void*)"no" ); + tree->insert( "dodo", (void*)"dodo" ); + tree->insert( "dodomanofdodo", (void*)"dodomanofdodo" ); + tree->insert( "dad", (void*)"dad" ); + + + tree->remove( "aaron", aaron ); + tree->remove( "apple", apple ); + + + char *searchString = "a"; + + int numMatches = tree->countMatches( searchString ); + + char **results = new char*[numMatches]; + + for( int j=0; jgetMatches( searchString, j, 2, + (void **)results ); + + for( int i=0; ilogPrintf( + Log::CRITICAL_ERROR_LEVEL, + "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return 0; + } + + + // read screen size from settings + char widthFound = false; + int readWidth = SettingsManager::getIntSetting( "screenWidth", + &widthFound ); + char heightFound = false; + int readHeight = SettingsManager::getIntSetting( "screenHeight", + &heightFound ); + + if( widthFound && heightFound ) { + // override hard-coded defaults + screenWidth = readWidth; + screenHeight = readHeight; + } + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Screen dimensions for fullscreen mode: %dx%d\n", + screenWidth, screenHeight ); + + + char fullscreenFound = false; + int readFullscreen = SettingsManager::getIntSetting( "fullscreen", + &fullscreenFound ); + + char fullscreen = true; + + if( readFullscreen == 0 ) { + fullscreen = false; + } + + + screen = + new ScreenGL( screenWidth, screenHeight, fullscreen, + 30, false, + "SleepIsDeath", NULL, NULL, NULL ); + + // may change if specified resolution is not supported + screenWidth = screen->getWidth(); + screenHeight = screen->getHeight(); + + + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL ); + + pixelZoomFactor = screenWidth / gameWidth; + + if( pixelZoomFactor * gameHeight > screenHeight ) { + // screen too short + pixelZoomFactor = screenHeight / gameHeight; + } + + + screen->setImageSize( pixelZoomFactor * gameWidth, + pixelZoomFactor * gameHeight ); + + + //SDL_ShowCursor( SDL_DISABLE ); + + + sceneHandler = new GameSceneHandler( screen ); + + + + // also do file-dependent part of init for GameSceneHandler here + // actually, constructor is file dependent anyway. + sceneHandler->initFromFiles(); + + + // hard to quit mode? + char hardToQuitFound = false; + int readHardToQuit = SettingsManager::getIntSetting( "hardToQuitMode", + &hardToQuitFound ); + + if( readHardToQuit == 1 ) { + hardToQuitMode = true; + } + + + + // auto-join mode? + char autoJoinFound = false; + int readAutoJoin = SettingsManager::getIntSetting( "autoJoin", + &autoJoinFound ); + + if( readAutoJoin == 1 || autoJoinAddress != NULL ) { + autoJoinMode = true; + } + + // auto-host mode? + char autoHostFound = false; + int readAutoHost = SettingsManager::getIntSetting( "autoHost", + &autoHostFound ); + + if( readAutoHost == 1 ) { + autoHostMode = true; + } + + + if( autoJoinMode && autoHostMode ) { + // only allow one + AppLog::info( + "Both autoJoin and autoHost set... defaulting to neither" ); + autoJoinMode = false; + autoHostMode = false; + } + + + + setMusicLoudness( 0.5 ); + startMusic(); + + + + // register cleanup function, since screen->start() will never return + atexit( cleanUpAtExit ); + + + + + screen->switchTo2DMode(); + + //glLineWidth( pixelZoomFactor ); + + screen->start(); + + + return 0; + } + + + + +GameSceneHandler::GameSceneHandler( ScreenGL *inScreen ) + : mScreen( inScreen ), + mStartTimeSeconds( time( NULL ) ), + mPaused( false ), + mPrintFrameRate( false ), + mNumFrames( 0 ), mFrameBatchSize( 100 ), + mFrameBatchStartTimeSeconds( time( NULL ) ), + mFrameBatchStartTimeMilliseconds( 0 ), + mBackgroundColor( 0, 0, 0, 1 ) { + + + mVolumeTarget = 0.5; + + + glClearColor( mBackgroundColor.r, + mBackgroundColor.g, + mBackgroundColor.b, + mBackgroundColor.a ); + + + // set external pointer so it can be used in calls below + sceneHandler = this; + + + mScreen->addMouseHandler( this ); + mScreen->addKeyboardHandler( this ); + mScreen->addSceneHandler( this ); + mScreen->addRedrawListener( this ); + + + Time::getCurrentTime( &mFrameBatchStartTimeSeconds, + &mFrameBatchStartTimeMilliseconds ); + + + + // set up main UI + mMainPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mMainPanelGuiTranslator = new GUITranslatorGL( mMainPanel, mScreen, + gameWidth ); + + guiTranslator = mMainPanelGuiTranslator; + + + mScreen->addSceneHandler( mMainPanelGuiTranslator ); + mScreen->addKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->addMouseHandler( mMainPanelGuiTranslator ); + + mGUIVisible = true; + + + // construct sub-panels, but only add the first one + + mWriteFailedPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + + mDemoCodePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mTitlePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mVolumePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mSelectPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mWaitPlayerPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mUPNPPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mJoinControllerPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mLoadingPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + + // make sure we can write to our main folder and resource cache folder + FILE *testFile = fopen( "testWrite.txt", "w" ); + + if( testFile == NULL ) { + // failed + mMainPanel->add( mWriteFailedPanel ); + mCurrentPanel = mWriteFailedPanel; + } + else { + fclose( testFile ); + + remove( "testWrite.txt" ); + + if( ! testResourceCacheWritePermissions() ) { + // failed + mMainPanel->add( mWriteFailedPanel ); + mCurrentPanel = mWriteFailedPanel; + } + else { + if( demoMode ) { + mMainPanel->add( mDemoCodePanel ); + mCurrentPanel = mDemoCodePanel; + } + else { + mMainPanel->add( mTitlePanel ); + mCurrentPanel = mTitlePanel; + } + } + } + + + } + + + +GameSceneHandler::~GameSceneHandler() { + mScreen->removeMouseHandler( this ); + mScreen->removeSceneHandler( this ); + mScreen->removeRedrawListener( this ); + + // remove before deleting, since we're not sure which one is still + // added + mMainPanel->remove( mWriteFailedPanel ); + mMainPanel->remove( mDemoCodePanel ); + mMainPanel->remove( mTitlePanel ); + mMainPanel->remove( mVolumePanel ); + mMainPanel->remove( mSelectPanel ); + mMainPanel->remove( mWaitPlayerPanel ); + mMainPanel->remove( mUPNPPanel ); + mMainPanel->remove( mJoinControllerPanel ); + mMainPanel->remove( mLoadingPanel ); + + + delete mWriteFailedPanel; + delete mDemoCodePanel; + delete mTitlePanel; + delete mVolumePanel; + delete mSelectPanel; + delete mWaitPlayerPanel; + delete mUPNPPanel; + delete mJoinControllerPanel; + delete mLoadingPanel; + + + + + // this will recursively delete all of our selector GUI components + delete mMainPanelGuiTranslator; + + delete mTextGL; + delete mTextGLFixedWidth; + + + } + + + +void GameSceneHandler::setLabelString( LabelGL *inLabel, + const char *inTranslationString, + double inScaleFactor ) { + char *labelString = + (char *)TranslationManager::translate( (char*)inTranslationString ); + + inLabel->setText( labelString ); + + TextGL *text = inLabel->getTextGL(); + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + double height = inScaleFactor * 8; + double width = height * strlen( labelString ); + + double actualDrawWidth = + height * text->measureTextWidth( labelString ); + + /* + // round to an integer number of pixels + actualDrawWidth = + round( actualDrawWidth * screenImageWidth ) + / + screenImageWidth; + */ + + double centerW = gameWidth / 2; + //0.5 * (double)( mScreen->getImageWidth() ) / screenImageHeight; + + double guiY = inLabel->getAnchorY(); + + + double labelX = centerW - 0.5 * actualDrawWidth; + + + /* + // round to an integer number of pixels + labelX = + round( labelX * screenImageWidth ) + / + screenImageWidth; + + guiY = + round( guiY * screenImageWidth ) + / + screenImageWidth; + */ + + + inLabel->setPosition( labelX, + guiY, + width, + height ); + } + + + +HighlightLabelGL *GameSceneHandler::createLabel( + double inGuiY, + const char *inTranslationString, + TextGL *inText ) { + + if( inText == NULL ) { + inText = mTextGL; + } + + HighlightLabelGL *returnLabel = + new HighlightLabelGL( 0, inGuiY, 0, 0, "", inText ); + + setLabelString( returnLabel, inTranslationString ); + + return returnLabel; + } + + + + + + +void GameSceneHandler::initFromFiles() { + + // translation language + File languageNameFile( NULL, "language.txt" ); + + if( languageNameFile.exists() ) { + char *languageNameText = languageNameFile.readFileContents(); + + SimpleVector *tokens = tokenizeString( languageNameText ); + + int numTokens = tokens->size(); + + // first token is name + if( numTokens > 0 ) { + char *languageName = *( tokens->getElement( 0 ) ); + + TranslationManager::setLanguage( languageName ); + } + else { + // default + + // TranslationManager already defaults to English, but + // it looks for the language files at runtime before we have set + // the current working directory. + + // Thus, we specify the default again here so that it looks + // for its language files again. + TranslationManager::setLanguage( "English" ); + } + + delete [] languageNameText; + + for( int t=0; tgetElement( t ) ); + } + delete tokens; + } + + + + // load text font + Image *fontImage = readTGA( "font_8_16.tga" ); + + if( fontImage == NULL ) { + // default + // blank font + fontImage = new Image( 256, 256, 4, true ); + } + + mTextGL = new TextGL( fontImage, + // use alpha + true, + // variable character width + false, + // extra space around each character, one pixel + 0.125, + // space is 4 pixels wide (out of 8) + 0.5 ); + largeText = mTextGL; + + mTextGLFixedWidth = new TextGL( fontImage, + // use alpha + true, + // fixed character width + true, + // extra space around each character + 0, + // space is full char width + 1.0 ); + + largeTextFixed = mTextGLFixedWidth; + + delete fontImage; + + + + + // now build gui panels based on labels, which depend on textGL + + mWriteFailedLabel = createLabel( 160, "writeFailed" ); + + // increase scale + setLabelString( mWriteFailedLabel, "writeFailed" ); + + mWriteFailedPanel->add( mWriteFailedLabel ); + + + + + mEnterDemoCodeLabel = createLabel( 175, "enterDemoCode" ); + mDemoCodePanel->add( mEnterDemoCodeLabel ); + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + + double height = 8; + double width = height * demoCodeLength; + + double centerW = gameWidth / 2; + + + char *defaultCode = + SettingsManager::getStringSetting( "demoCode" ); + + char *fieldDefault = (char*)""; + if( defaultCode != NULL ) { + fieldDefault = defaultCode; + } + + mEnterDemoCodeField = new TextFieldGL( centerW - 0.5 * width, + 145, + width, + height, + 1, + fieldDefault, + mTextGLFixedWidth, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + demoCodeLength, + true ); + + mDemoCodePanel->add( mEnterDemoCodeField ); + + + if( demoMode ) { + mEnterDemoCodeField->setEnabled( true ); + mEnterDemoCodeField->setFocus( true ); + mEnterDemoCodeField->lockFocus( true ); + } + + + + if( defaultCode != NULL ) { + + mEnterDemoCodeField->setCursorPosition( strlen( defaultCode ) ); + + + if( demoMode ) { + + // don't destroy this + char *enteredCode = mEnterDemoCodeField->getText(); + + if( strlen( enteredCode ) > 0 ) { + + // run with this code + // disable further input + mEnterDemoCodeField->setEnabled( false ); + mEnterDemoCodeField->setFocus( false ); + mEnterDemoCodeField->lockFocus( false ); + + setLabelString( mEnterDemoCodeLabel, + "checkingCode" ); + + // start + codeChecker = new DemoCodeChecker( enteredCode ); + } + } + + + delete [] defaultCode; + } + + + + + + + mTitleLabel = createLabel( 160, "title" ); + + mTitlePanel->add( mTitleLabel ); + + + + mVolumeLabel = createLabel( 160, "volume" ); + + mVolumePanel->add( mVolumeLabel ); + + + mVolumeDoneLabel = createLabel( 48, "volumeDone" ); + + mVolumePanel->add( mVolumeDoneLabel ); + + + + Color thumbColor( .5, .5, .5, .5 ); + Color borderColor( .35, .35, .35, .35 ); + + mVolumeSlider = new SliderGL( 160 - 24, 104, + 48, 12, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 0.5, 0, 1 ), + thumbColor.copy(), + borderColor.copy(), + 1, 4, 1 ); + mVolumePanel->add( mVolumeSlider ); + + // volume starts in middle + // avoid round-off errors + mVolumeSlider->setThumbPosition( (int)(0.5 * 96) / 96.0 ); + + mVolumeSlider->addActionListener( this ); + + + mSelectLabels[0] = + mControlLocalLabel = createLabel( 190, "control_local" ); + + mSelectLabels[1] = + mControlRemoteLabel = createLabel( 160, "control_remote" ); + + mSelectLabels[2] = + mJoinAsPlayerLabel = createLabel( 130, "joinAsPlayer" ); + + mSelectPanel->add( mControlLocalLabel ); + mSelectPanel->add( mControlRemoteLabel ); + mSelectPanel->add( mJoinAsPlayerLabel ); + + mSelectLabels[0]->setHighlight( true ); + mSelectHighlightIndex = 0; + + mSelectLabels[0]->addActionListener( this ); + mSelectLabels[1]->addActionListener( this ); + mSelectLabels[2]->addActionListener( this ); + + + + HighlightLabelGL *escLabel = createLabel( 48, "escToQuit" ); + + mSelectPanel->add( escLabel ); + + + + char *portString = autoSprintf( "%s %d", + TranslationManager::translate( "portTip" ), + Connection::getPort() ); + mPortTipLabel = createLabel( 48, portString ); + delete [] portString; + + + + mWaitPlayerMessageLabel = createLabel( 205, "" ); + + + + mWaitPlayerLabel = createLabel( 175, "waitingForPlayer" ); + + HostAddress *local = HostAddress::getNumericalLocalAddress(); + + + if( local != NULL ) { + addressString = stringDuplicate( local->mAddressString ); + delete local; + } + else { + addressString = stringDuplicate( "???.???.???.???" ); + } + + + mAddressLabel = createLabel( 145, addressString, + mTextGLFixedWidth ); + + + mDoNotWaitTipLabel = createLabel( 115, "doNotWaitTip" ); + + mWaitPlayerPanel->add( mWaitPlayerMessageLabel ); + mWaitPlayerPanel->add( mWaitPlayerLabel ); + mWaitPlayerPanel->add( mAddressLabel ); + mWaitPlayerPanel->add( mDoNotWaitTipLabel ); + mWaitPlayerPanel->add( mPortTipLabel ); + + mPortTipLabel->setEnabled( false ); + + + + mUPNPLabel = createLabel( 160, "upnpTrying" ); + mUPNPPanel->add( mUPNPLabel ); + + + + mEnterAddressLabel = createLabel( 175, "enterAddress" ); + mJoinControllerPanel->add( mEnterAddressLabel ); + + + mLoadingLabel = createLabel( 160, "loading" ); + + mLoadingPanel->add( mLoadingLabel ); + + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + + height = 8; + width = height * maxAddressLength; + + centerW = gameWidth / 2; + + + char *defaultAddress = NULL; + + if( autoJoinAddress != NULL ) { + defaultAddress = stringDuplicate( autoJoinAddress ); + } + else { + defaultAddress = + SettingsManager::getStringSetting( "defaultServerAddress" ); + } + + fieldDefault = (char*)""; + if( defaultAddress != NULL ) { + fieldDefault = defaultAddress; + } + + + mEnterAddressField = new TextFieldGL( centerW - 0.5 * width, + 145, + width, + height, + 1, + fieldDefault, + mTextGLFixedWidth, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + maxAddressLength, + true ); + + mEnterAddressField->setCursorPosition( strlen( fieldDefault ) ); + + mJoinControllerPanel->add( mEnterAddressField ); + + + if( defaultAddress != NULL ) { + delete [] defaultAddress; + } + + + + + + } + + + + + +void GameSceneHandler::drawScene() { + /* + glClearColor( mBackgroundColor->r, + mBackgroundColor->g, + mBackgroundColor->b, + mBackgroundColor->a ); + */ + + glDisable( GL_TEXTURE_2D ); + glDisable( GL_CULL_FACE ); + glDisable( GL_DEPTH_TEST ); + + + /* + // sky + glColor4d( 0.75, 0.75, 0.75, 1.0 ); + + glBegin( GL_QUADS ); { + glVertex2d( -1, 1 ); + glVertex2d( 1, 1 ); + glVertex2d( 1, -1 ); + glVertex2d( -1, -1 ); + } + glEnd(); + */ + } + + + +void GameSceneHandler::mouseMoved( int inX, int inY ) { + } + + + +void GameSceneHandler::mouseDragged( int inX, int inY ) { + } + + + + +void GameSceneHandler::mousePressed( int inX, int inY ) { + } + + + +void GameSceneHandler::mouseReleased( int inX, int inY ) { + // actually, WHY do we let people mouse-click from title? + // rest of menu system is all keyboard-driven + // this sets up a false expectation that the mouse will do something + // in the menus + /* + if( mGUIVisible ) { + + if( mMainPanel->contains( mTitlePanel ) ) { + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mVolumePanel ); + mCurrentPanel = mVolumePanel; + + // test sounds on + for( int i=0; ilogPrintf( + Log::DETAIL_LEVEL, + "Frame rate = %f frames/second\n", frameRate ); + + Time::getCurrentTime( &mFrameBatchStartTimeSeconds, + &mFrameBatchStartTimeMilliseconds ); + } + } + + + // process old connections to clear pending ops and destroy them + for( int i=0; istep() == false ) { + // done + AppLog::info( "Destroying old connection.\n" ); + + delete c; + + oldConnections.deleteElement( i ); + + i--; + } + } + + + + // should we start a game? + if( mMainPanel->contains( mLoadingPanel ) ) { + if( loadingDrawnOnce ) { + + // loading panel drawn at least once + + if( isControllerGame ) { + game = new ControllerGame( mScreen ); + } + else { + // back to default sounds (in case we're Player in a + // v13 game) + setDefaultMusicSounds(); + + game = new PlayerGame( mScreen ); + isControllerGame = false; + } + + if( mGUIVisible ) { + // hide menu gui to start the game + + mMainPanel->remove( mCurrentPanel ); + mCurrentPanel = NULL; + + mScreen->removeSceneHandler( mMainPanelGuiTranslator ); + mScreen->removeKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->removeMouseHandler( mMainPanelGuiTranslator ); + mGUIVisible = false; + + + // add game handlers instead + mScreen->addSceneHandler( game ); + + // insert as first handler to catch quit events + mScreen->addKeyboardHandler( game, true ); + mScreen->addMouseHandler( game ); + mScreen->addRedrawListener( game ); + + game->setScreen( screen ); + } + } + else { + loadingDrawnOnce = true; + } + + } + + + + + + if( connection != NULL ) { + + if( ! connection->isError() ) { + + char workLeft = connection->step(); + + + if( connection->isConnected() && game == NULL ) { + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + + mMainPanel->remove( mJoinControllerPanel ); + + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + loadingDrawnOnce = false; + + isControllerGame = false; + } + else if( mMainPanel->contains( mWaitPlayerPanel ) ) { + mMainPanel->remove( mWaitPlayerPanel ); + + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + loadingDrawnOnce = false; + + isControllerGame = true; + } + // we construct the game outside the check for a connection + // (based on presence of loading panel) so that same + // construction code can be used even if player hits G + // to start w/out a connection + + /* + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + // got connection as player + + // back to default sounds (in case we're Player in a + // v13 game) + setDefaultMusicSounds(); + + + game = new PlayerGame( mScreen ); + isControllerGame = false; + } + else if( mMainPanel->contains( mWaitPlayerPanel ) ) { + // got connection as controller + game = new ControllerGame( mScreen ); + isControllerGame = true; + } + */ + + + + + // if connected, keep stepping until no network work left + while( workLeft ) { + workLeft = connection->step(); + } + } + + } + else { + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Connection failed: %s\n", connection->getErrorString() ); + + oldConnections.push_back( connection ); + + connection = NULL; + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + + // re-enable input + mEnterAddressField->setEnabled( true ); + mEnterAddressField->setFocus( true ); + mEnterAddressField->lockFocus( true ); + + setLabelString( mEnterAddressLabel, + "joiningFailed" ); + } + } + } + else { + // no connection... is a Controller game already running? + if( game != NULL && isControllerGame ) { + + // are any old connections (which might block hosting on port) + // still waiting to be destroyed? + + if( oldConnections.size() == 0 ) { + + // clear to start a new one + AppLog::info( + "Trying to receive a new Player connection...\n" ); + // start a server + connection = new Connection(); + + game->resetTimer(); + } + } + } + + + + // process old codeCheckers to clear pending ops and destroy them + for( int i=0; istep() == false ) { + // done + AppLog::info( "Destroying old codeChecker.\n" ); + + delete c; + + oldCodeCheckers.deleteElement( i ); + + i--; + } + } + + if( codeChecker != NULL ) { + + if( codeChecker->isError() ) { + if( mMainPanel->contains( mDemoCodePanel ) ) { + + // re-enable input + mEnterDemoCodeField->setEnabled( true ); + mEnterDemoCodeField->setFocus( true ); + mEnterDemoCodeField->lockFocus( true ); + + + char *message = codeChecker->getErrorString(); + + setLabelString( mEnterDemoCodeLabel, message ); + } + oldCodeCheckers.push_back( codeChecker ); + + codeChecker = NULL; + } + else { + + char checkerWorkLeft = codeChecker->step(); + + if( ! checkerWorkLeft + && ! codeChecker->isError() + && codeChecker->codePermitted() ) { + + // will have error if not permitted, which is handled above + + oldCodeCheckers.push_back( codeChecker ); + + codeChecker = NULL; + + if( mMainPanel->contains( mDemoCodePanel ) ) { + // move on to title + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mTitlePanel ); + mCurrentPanel = mTitlePanel; + } + } + } + + } + + + + + + if( game != NULL ) { + game->step(); + + if( game->isQuitting() ) { + + // back out and restore menu gui + if( !mGUIVisible ) { + + mScreen->addSceneHandler( mMainPanelGuiTranslator ); + mScreen->addKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->addMouseHandler( mMainPanelGuiTranslator ); + mGUIVisible = true; + + + + mScreen->removeSceneHandler( game ); + mScreen->removeKeyboardHandler( game ); + mScreen->removeMouseHandler( game ); + mScreen->removeRedrawListener( game ); + } + + delete game; + game = NULL; + + if( connection != NULL ) { + oldConnections.push_back( connection ); + connection = NULL; + } + + // clear music + for( int si=0; siadd( mSelectPanel ); + mCurrentPanel = mSelectPanel; + } + else { + // close UPNP port first + + mMainPanel->add( mUPNPPanel ); + mCurrentPanel = mUPNPPanel; + setLabelString( mUPNPLabel, "upnpCloseTrying" ); + + // wait for it to draw once before blocking + tryUPNPClose = false; + } + } + + } + + + // gradually adjust loudness toward target to avoid clicks + + double trueVolume = getMusicLoudness(); + if( mVolumeTarget != trueVolume ) { + + double newTrueVolume = trueVolume; + + if( mVolumeTarget > trueVolume ) { + newTrueVolume += 0.06; + + if( newTrueVolume > mVolumeTarget ) { + newTrueVolume = mVolumeTarget; + } + } + if( mVolumeTarget < trueVolume ) { + newTrueVolume -= 0.06; + + if( newTrueVolume < mVolumeTarget ) { + newTrueVolume = mVolumeTarget; + } + } + + setMusicLoudness( newTrueVolume ); + } + + + + + // handle auto-starts here + if( autoHostMode && ! mMainPanel->contains( mWaitPlayerPanel ) ) { + // done, don't re-trigger if player backs out of it + autoHostMode = false; + + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + + // start as server + connection = new Connection(); + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mWaitPlayerPanel ); + mCurrentPanel = mWaitPlayerPanel; + } + + if( autoJoinMode && ! mMainPanel->contains( mJoinControllerPanel ) ) { + // done, don't re-trigger if player backs out of it + autoJoinMode = false; + + char *enteredAddress = mEnterAddressField->getText(); + + if( strlen( enteredAddress ) > 0 ) { + // disable further input + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mEnterAddressLabel, + "joiningController" ); + + // start as client + connection = new Connection( enteredAddress ); + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mJoinControllerPanel ); + mCurrentPanel = mJoinControllerPanel; + } + } + + + if( mMainPanel->contains( mUPNPPanel ) && !tryUPNP && !upnpPortOpen ) { + // try blocking op on next frame, so that panel can draw at least once + tryUPNP = true; + } + else if( tryUPNP ) { + tryUPNP = false; + + char *externalIP; + + int result = mapPort( Connection::getPort(), "Sleep Is Death", + 2000, + &externalIP ); + + if( result == 1 ) { + upnpPortOpen = true; + if( externalIP != NULL ) { + setLabelString( mAddressLabel, externalIP ); + + delete [] addressString; + addressString = externalIP; + } + } + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mWaitPlayerPanel ); + mCurrentPanel = mWaitPlayerPanel; + + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + + // start as server + connection = new Connection(); + + + if( result == 1 ) { + setLabelString( mWaitPlayerMessageLabel, "upnpSuccess" ); + mPortTipLabel->setEnabled( false ); + } + else { + setLabelString( mWaitPlayerMessageLabel, "upnpFailure" ); + mPortTipLabel->setEnabled( true ); + } + } + + + if( mMainPanel->contains( mUPNPPanel ) && upnpPortOpen && !tryUPNPClose ) { + // try blocking op on next frame, so that panel can draw at least once + tryUPNPClose = true; + } + else if( tryUPNPClose ) { + tryUPNPClose = false; + + int result = unmapPort( Connection::getPort(), 2000 ); + + if( result == 1 ) { + upnpPortOpen = false; + } + + if( game == NULL ) { + // not in the middle of a game, back out to select panel + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + + + // back to local address + HostAddress *local = HostAddress::getNumericalLocalAddress(); + + + if( local != NULL ) { + delete [] addressString; + + addressString = stringDuplicate( local->mAddressString ); + + setLabelString( mAddressLabel, addressString ); + + delete local; + } + + } + else { + // quitting, but we just closed the UPNP port first + exit( 0 ); + } + } + + } + + + +static unsigned char lastKeyPressed = '\0'; + + +void GameSceneHandler::keyPressed( + unsigned char inKey, int inX, int inY ) { + + if( enableSlowdownKeys ) { + if( inKey == '^' ) { + // slow + mScreen->setMaxFrameRate( 2 ); + } + if( inKey == '&' ) { + // normal + mScreen->setMaxFrameRate( 30 ); + } + } + + + if( mGUIVisible ) { + + if( mMainPanel->contains( mTitlePanel ) || + mMainPanel->contains( mVolumePanel ) ) { + if( !hardToQuitMode ) { + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + exit( 0 ); + } + } + else { + // # followed by ESC + if( lastKeyPressed == '#' && inKey == 27 ) { + exit( 0 ); + } + lastKeyPressed = inKey; + } + + if( inKey != 'q' && inKey != 'Q' && inKey != 27 && inKey != '#' ) { + // any other key advances to next screen + if( mMainPanel->contains( mTitlePanel ) ) { + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mVolumePanel ); + mCurrentPanel = mVolumePanel; + + // test sounds on + // emulate old 1-grid test run (rising tones) + for( int i=0; icontains( mVolumePanel ) ) { + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + + // test sounds off + for( int i=0; icontains( mSelectPanel ) ) { + + if( !hardToQuitMode ) { + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + exit( 0 ); + } + } + else { + // # followed by ESC + if( lastKeyPressed == '#' && inKey == 27 ) { + exit( 0 ); + } + lastKeyPressed = inKey; + } + + if( inKey != 'q' && inKey != 'Q' && inKey != 27 && inKey != '#' ) { + // any other key press triggers selection + processSelection( mSelectHighlightIndex ); + } + } + else { + if( mMainPanel->contains( mWriteFailedPanel ) ) { + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + // q or escape + exit( 0 ); + } + } + + + if( mMainPanel->contains( mDemoCodePanel ) ) { + // look for enter + if( inKey == 13 && mEnterDemoCodeField->isFocused() ) { + + // don't destroy this + char *enteredCode = mEnterDemoCodeField->getText(); + + if( strlen( enteredCode ) > 0 ) { + // disable further input + mEnterDemoCodeField->setEnabled( false ); + mEnterDemoCodeField->setFocus( false ); + mEnterDemoCodeField->lockFocus( false ); + + setLabelString( mEnterDemoCodeLabel, + "checkingCode" ); + + // save this for next time + SettingsManager::setSetting( "demoCode", + enteredCode ); + + // start + codeChecker = new DemoCodeChecker( enteredCode ); + } + } + else if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + // q or escape + exit( 0 ); + } + } + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + // look for enter + if( inKey == 13 && mEnterAddressField->isFocused() ) { + + // don't destroy this + char *enteredAddress = mEnterAddressField->getText(); + + if( strlen( enteredAddress ) > 0 ) { + // disable further input + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + + setLabelString( mEnterAddressLabel, + "joiningController" ); + + // start as client + connection = new Connection( enteredAddress ); + } + } + } + + if( mMainPanel->contains( mWaitPlayerPanel ) ) { + // look for "g" to go ahaead + if( inKey == 'g' || inKey == 'G' ) { + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + isControllerGame = true; + loadingDrawnOnce = false; + + /* + game = new ControllerGame( mScreen ); + isControllerGame = true; + + // hide menu gui to start the game + + mMainPanel->remove( mCurrentPanel ); + mCurrentPanel = NULL; + + mScreen->removeSceneHandler( mMainPanelGuiTranslator ); + mScreen->removeKeyboardHandler( mMainPanelGuiTranslator ); + mGUIVisible = false; + + + // add game handlers instead + mScreen->addSceneHandler( game ); + mScreen->addKeyboardHandler( game ); + mScreen->addMouseHandler( game ); + mScreen->addRedrawListener( game ); + + game->setScreen( screen ); + */ + } + } + + + + // q or escape pressed in any sub-panel + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + + // back up + + // wait for this connection to finish before destroying it + + if( connection != NULL ) { + oldConnections.push_back( connection ); + connection = NULL; + } + + mMainPanel->remove( mCurrentPanel ); + + if( upnpPortOpen && mCurrentPanel == mWaitPlayerPanel ) { + // close upnp port first + AppLog::info( "Setting upnp panel\n" ); + mMainPanel->add( mUPNPPanel ); + mCurrentPanel = mUPNPPanel; + + setLabelString( mUPNPLabel, "upnpCloseTrying" ); + + // show upnp panel at least once before blocking + tryUPNPClose = false; + } + else { + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + } + + } + } + + + } + + + + /* + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + if( !showingQuitQuestion ) { + oldStatusText = stringDuplicate( mStatusLabel->getText() ); + + showingQuitQuestion = true; + + setLabelString( mStatusLabel, "quitQuestion" ); + } + } + // answer to quit question? + else if( showingQuitQuestion && ( inKey == 'y' || inKey == 'Y' ) ) { + delete [] oldStatusText; + oldStatusText = NULL; + + exit( 0 ); + } + else if( showingQuitQuestion && ( inKey == 'n' || inKey == 'N' ) ) { + setLabelString( mStatusLabel, oldStatusText ); + + delete [] oldStatusText; + oldStatusText = NULL; + + showingQuitQuestion = false; + } + */ + } + + + +void GameSceneHandler::keyReleased( + unsigned char inKey, int inX, int inY ) { + + } + + + +void GameSceneHandler::specialKeyPressed( + int inKey, int inX, int inY ) { + + /* + // keep key states for later, whether we act on them now or not + switch( inKey ) { + case MG_KEY_LEFT: + mLeftKeyDown = true; + break; + case MG_KEY_RIGHT: + mRightKeyDown = true; + break; + } + */ + + + switch( inKey ) { + case MG_KEY_UP: + if( mGUIVisible && + mMainPanel->contains( mSelectPanel ) ) { + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( false ); + + mSelectHighlightIndex --; + if( mSelectHighlightIndex < 0 ) { + mSelectHighlightIndex = 2; + } + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( true ); + } + break; + case MG_KEY_DOWN: + if( mGUIVisible && + mMainPanel->contains( mSelectPanel ) ) { + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( false ); + + mSelectHighlightIndex ++; + if( mSelectHighlightIndex > 2 ) { + mSelectHighlightIndex = 0; + } + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( true ); + } + break; + } + + } + + + +void GameSceneHandler::specialKeyReleased( + int inKey, int inX, int inY ) { + + /* + // keep key states for later, whether we act on them now or not + switch( inKey ) { + case MG_KEY_LEFT: + mLeftKeyDown = false; + break; + case MG_KEY_RIGHT: + mRightKeyDown = false; + break; + } + */ + } + + + +void GameSceneHandler::actionPerformed( GUIComponent *inTarget ) { + + if( inTarget == mVolumeSlider ) { + mVolumeTarget = mVolumeSlider->getThumbPosition(); + } + else if( inTarget == mSelectLabels[0] ) { + processSelection( 0 ); + } + else if( inTarget == mSelectLabels[1] ) { + processSelection( 1 ); + } + else if( inTarget == mSelectLabels[2] ) { + processSelection( 2 ); + } + } + + + +void GameSceneHandler::processSelection( int inSelection ) { + + GUIPanelGL *panelToShow = NULL; + + switch( inSelection ) { + case 0: + panelToShow = mWaitPlayerPanel; + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + setLabelString( mWaitPlayerMessageLabel, "" ); + mPortTipLabel->setEnabled( true ); + + // start as server + connection = new Connection(); + + break; + case 1: + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + if( upnpPortOpen ) { + // already open, maybe from failed close before? + + panelToShow = mWaitPlayerPanel; + + setLabelString( mWaitPlayerLabel, + "waitingForPlayer" ); + setLabelString( mWaitPlayerMessageLabel, "" ); + + // start as server + connection = new Connection(); + } + else { + // open it + panelToShow = mUPNPPanel; + + + setLabelString( mUPNPLabel, "upnpTrying" ); + + // wait until panel draws at least once + tryUPNP = false; + } + break; + case 2: + panelToShow = mJoinControllerPanel; + + setLabelString( mEnterAddressLabel, "enterAddress" ); + mEnterAddressField->setFocus( true ); + mEnterAddressField->lockFocus( true ); + mEnterAddressField->setEnabled( true ); + + // don't pass this key press through to field + mEnterAddressField->ignoreNextKey(); + break; + } + + if( panelToShow != NULL ) { + mMainPanel->remove( mSelectPanel ); + mMainPanel->add( panelToShow ); + mCurrentPanel = panelToShow; + } + } + + + + + diff --git a/gameSource/gameNoMain.cpp b/gameSource/gameNoMain.cpp new file mode 100644 index 0000000..8a0e6a5 --- /dev/null +++ b/gameSource/gameNoMain.cpp @@ -0,0 +1,2260 @@ +/* + * Modification History + * + * 2008-September-11 Jason Rohrer + * Created. Copied from Cultivation. + */ + + +#include +#include +#include +#include +#include + + +// let SDL override our main function with SDLMain +#include + + +// must do this before SDL include to prevent WinMain linker errors on win32 +int mainFunction( int inArgCount, char **inArgs ); + +int mainBLOCKED( int inArgCount, char **inArgs ) { + return mainFunction( inArgCount, inArgs ); + } + + +#include + + + +#include "minorGems/graphics/openGL/ScreenGL.h" +#include "minorGems/graphics/openGL/SceneHandlerGL.h" +#include "minorGems/graphics/Color.h" + +#include "minorGems/graphics/openGL/gui/GUIPanelGL.h" +#include "minorGems/graphics/openGL/gui/GUITranslatorGL.h" +#include "minorGems/graphics/openGL/gui/TextGL.h" +#include "minorGems/graphics/openGL/gui/LabelGL.h" +#include "minorGems/graphics/openGL/gui/TextFieldGL.h" +#include "minorGems/graphics/openGL/gui/SliderGL.h" + + + +#include "minorGems/system/Time.h" +#include "minorGems/system/Thread.h" + +#include "minorGems/io/file/File.h" + +#include "minorGems/network/HostAddress.h" + +#include "minorGems/network/upnp/portMapping.h" + + +#include "minorGems/util/SettingsManager.h" +#include "minorGems/util/TranslationManager.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" + + +#include "minorGems/util/log/AppLog.h" +#include "minorGems/util/log/FileLog.h" + + +#include "common.h" +#include "HighlightLabelGL.h" +#include "Connection.h" +#include "GameHalf.h" +#include "PlayerGame.h" +#include "ControllerGame.h" +#include "musicPlayer.h" +#include "Song.h" +#include "DemoCodeChecker.h" + + +// from musicPlayer.cpp +extern char noteToggles[PARTS][S][N][N]; +extern int partLengths[PARTS]; + + + +// some settings + +// size of game image +int gameWidth = 320; +int gameHeight = 240; + +// size of screen for fullscreen mode +int screenWidth = 640; +int screenHeight = 480; + + +char hardToQuitMode = false; + + +double musicTrackFadeLevel = 1.0; + + +char autoJoinMode = false; +char autoHostMode = false; + + + +// ^ and & keys to slow down and speed up for testing +char enableSlowdownKeys = false; +//char enableSlowdownKeys = true; + + +// is demo mode on? If so, it checks server to see if it is still allowed +// to run. +char demoMode = false; + +int demoCodeLength = 10; + +DemoCodeChecker *codeChecker = NULL; + + +// track old code checkers so that we can destroy them after they unblock +SimpleVector oldCodeCheckers; + + + + +// these are shared by other modules as extern +Connection *connection = NULL; +TextGL *largeText = NULL; +TextGL *largeTextFixed = NULL; +char *addressString = NULL; + + +// should we try UPNP search (blocks) on next redraw? +char tryUPNP = false; +char tryUPNPClose = false; +char upnpPortOpen = false; + + +// track old clients so that we can destroy them after they unblock +SimpleVector oldConnections; + + +GameHalf *game = NULL; +char isControllerGame = false; +char loadingDrawnOnce = false; + + + +class GameSceneHandler : + public SceneHandlerGL, public MouseHandlerGL, public KeyboardHandlerGL, + public RedrawListenerGL, public ActionListener { + + public: + + /** + * Constructs a sceen handler. + * + * @param inScreen the screen to interact with. + * Must be destroyed by caller after this class is destroyed. + */ + GameSceneHandler( ScreenGL *inScreen ); + + virtual ~GameSceneHandler(); + + + + /** + * Executes necessary init code that reads from files. + * + * Must be called before using a newly-constructed GameSceneHandler. + * + * This call assumes that the needed files are in the current working + * directory. + */ + void initFromFiles(); + + + + ScreenGL *mScreen; + + + + + + + + + // implements the SceneHandlerGL interface + virtual void drawScene(); + + // implements the MouseHandlerGL interface + virtual void mouseMoved( int inX, int inY ); + virtual void mouseDragged( int inX, int inY ); + virtual void mousePressed( int inX, int inY ); + virtual void mouseReleased( int inX, int inY ); + + // implements the KeyboardHandlerGL interface + virtual char isFocused() { + // always focused + return true; + } + virtual void keyPressed( unsigned char inKey, int inX, int inY ); + virtual void specialKeyPressed( int inKey, int inX, int inY ); + virtual void keyReleased( unsigned char inKey, int inX, int inY ); + virtual void specialKeyReleased( int inKey, int inX, int inY ); + + // implements the RedrawListener interface + virtual void fireRedraw(); + + + // implements the ActionListener interface + virtual void actionPerformed( GUIComponent *inTarget ); + + + + protected: + + // sets the string on a label and re-centers it + void setLabelString( LabelGL *inLabel, + char *inTranslationString, + double inScaleFactor = 1.0 ); + + // creates a centerd label at a particular height + HighlightLabelGL *createLabel( double inGuiY, + char *inTranslationString, + TextGL *inText = NULL ); + + + // the time that the last frame was drawn + unsigned long mLastFrameSeconds; + unsigned long mLastFrameMilliseconds; + + // our current frame rate + unsigned long mFrameMillisecondDelta; + + int mStartTimeSeconds; + + char mPaused; + + double mMaxFrameRate; + + char mPrintFrameRate; + unsigned long mNumFrames; + unsigned long mFrameBatchSize; + unsigned long mFrameBatchStartTimeSeconds; + unsigned long mFrameBatchStartTimeMilliseconds; + + + + Color mBackgroundColor; + + + + + // for game selection display + char mGUIVisible; + + GUIPanelGL *mMainPanel; + GUITranslatorGL *mMainPanelGuiTranslator; + + TextGL *mTextGL; + TextGL *mTextGLFixedWidth; + + GUIPanelGL *mCurrentPanel; + + + GUIPanelGL *mWriteFailedPanel; + HighlightLabelGL *mWriteFailedLabel; + + + + GUIPanelGL *mDemoCodePanel; + HighlightLabelGL *mEnterDemoCodeLabel; + TextFieldGL *mEnterDemoCodeField; + + + + + GUIPanelGL *mTitlePanel; + HighlightLabelGL *mTitleLabel; + + + GUIPanelGL *mVolumePanel; + HighlightLabelGL *mVolumeLabel; + HighlightLabelGL *mVolumeDoneLabel; + + SliderGL *mVolumeSlider; + + // avoid abrupt volume changes that cause clicks + double mVolumeTarget; + + + + GUIPanelGL *mSelectPanel; + HighlightLabelGL *mControlLocalLabel; + HighlightLabelGL *mControlRemoteLabel; + HighlightLabelGL *mJoinAsPlayerLabel; + + // track as array to make toggling easier + HighlightLabelGL *mSelectLabels[3]; + int mSelectHighlightIndex; + + + GUIPanelGL *mWaitPlayerPanel; + HighlightLabelGL *mWaitPlayerMessageLabel; + HighlightLabelGL *mWaitPlayerLabel; + HighlightLabelGL *mAddressLabel; + HighlightLabelGL *mDoNotWaitTipLabel; + + + GUIPanelGL *mUPNPPanel; + HighlightLabelGL *mUPNPLabel; + + + GUIPanelGL *mJoinControllerPanel; + HighlightLabelGL *mEnterAddressLabel; + TextFieldGL *mEnterAddressField; + + + + GUIPanelGL *mLoadingPanel; + HighlightLabelGL *mLoadingLabel; + + }; + + + +GameSceneHandler *sceneHandler; +ScreenGL *screen; +GUITranslatorGL *guiTranslator; + + +// how many pixels wide is each game pixel drawn as? +int pixelZoomFactor; + + + +int maxAddressLength = 15; + + + + +// function that destroys object when exit is called. +// exit is the only way to stop the loop in ScreenGL +void cleanUpAtExit() { + AppLog::info( "exiting\n" ); + + + if( game != NULL ) { + delete game; + } + + + delete sceneHandler; + delete screen; + + + if( codeChecker != NULL ) { + delete codeChecker; + } + + for( int i=0; iinsert( "apple", (void*)apple ); + //tree->insert( "dapper", (void*)"dapper" ); + //tree->insert( "relapse", (void*)"relapse" ); + tree->insert( "apron", (void*)"apron" ); + + char* aaron = "aaron"; + + tree->insert( "aaron", (void*)aaron ); + + tree->insert( "no", (void*)"no" ); + tree->insert( "dodo", (void*)"dodo" ); + tree->insert( "dodomanofdodo", (void*)"dodomanofdodo" ); + tree->insert( "dad", (void*)"dad" ); + + + tree->remove( "aaron", aaron ); + tree->remove( "apple", apple ); + + + char *searchString = "a"; + + int numMatches = tree->countMatches( searchString ); + + char **results = new char*[numMatches]; + + for( int j=0; jgetMatches( searchString, j, 2, + (void **)results ); + + for( int i=0; ilogPrintf( + Log::CRITICAL_ERROR_LEVEL, + "Couldn't initialize SDL: %s\n", SDL_GetError() ); + return 0; + } + + + // read screen size from settings + char widthFound = false; + int readWidth = SettingsManager::getIntSetting( "screenWidth", + &widthFound ); + char heightFound = false; + int readHeight = SettingsManager::getIntSetting( "screenHeight", + &heightFound ); + + if( widthFound && heightFound ) { + // override hard-coded defaults + screenWidth = readWidth; + screenHeight = readHeight; + } + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Screen dimensions for fullscreen mode: %dx%d\n", + screenWidth, screenHeight ); + + + char fullscreenFound = false; + int readFullscreen = SettingsManager::getIntSetting( "fullscreen", + &fullscreenFound ); + + char fullscreen = true; + + if( readFullscreen == 0 ) { + fullscreen = false; + } + + + screen = + new ScreenGL( screenWidth, screenHeight, fullscreen, + "SleepIsDeath", NULL, NULL, NULL ); + + // may change if specified resolution is not supported + screenWidth = screen->getWidth(); + screenHeight = screen->getHeight(); + + + SDL_EnableKeyRepeat( SDL_DEFAULT_REPEAT_DELAY, + SDL_DEFAULT_REPEAT_INTERVAL ); + + pixelZoomFactor = screenWidth / gameWidth; + + if( pixelZoomFactor * gameHeight > screenHeight ) { + // screen too short + pixelZoomFactor = screenHeight / gameHeight; + } + + + screen->setImageSize( pixelZoomFactor * gameWidth, + pixelZoomFactor * gameHeight ); + + + //SDL_ShowCursor( SDL_DISABLE ); + + + sceneHandler = new GameSceneHandler( screen ); + + + + // also do file-dependent part of init for GameSceneHandler here + // actually, constructor is file dependent anyway. + sceneHandler->initFromFiles(); + + + // hard to quit mode? + char hardToQuitFound = false; + int readHardToQuit = SettingsManager::getIntSetting( "hardToQuitMode", + &hardToQuitFound ); + + if( readHardToQuit == 1 ) { + hardToQuitMode = true; + } + + + + // auto-join mode? + char autoJoinFound = false; + int readAutoJoin = SettingsManager::getIntSetting( "autoJoin", + &autoJoinFound ); + + if( readAutoJoin == 1 ) { + autoJoinMode = true; + } + + // auto-host mode? + char autoHostFound = false; + int readAutoHost = SettingsManager::getIntSetting( "autoHost", + &autoHostFound ); + + if( readAutoHost == 1 ) { + autoHostMode = true; + } + + + if( autoJoinMode && autoHostMode ) { + // only allow one + AppLog::info( + "Both autoJoin and autoHost set... defaulting to neither" ); + autoJoinMode = false; + autoHostMode = false; + } + + + + setMusicLoudness( 0.5 ); + startMusic(); + + + + // register cleanup function, since screen->start() will never return + atexit( cleanUpAtExit ); + + + + + screen->switchTo2DMode(); + + //glLineWidth( pixelZoomFactor ); + + screen->start(); + + + return 0; + } + + + + +GameSceneHandler::GameSceneHandler( ScreenGL *inScreen ) + : mScreen( inScreen ), + mFrameMillisecondDelta( 0 ), + mStartTimeSeconds( time( NULL ) ), + mPaused( false ), + mMaxFrameRate( 30 ), + mPrintFrameRate( false ), + mNumFrames( 0 ), mFrameBatchSize( 100 ), + mFrameBatchStartTimeSeconds( time( NULL ) ), + mFrameBatchStartTimeMilliseconds( 0 ), + mBackgroundColor( 0, 0, 0, 1 ) { + + + mVolumeTarget = 0.5; + + + glClearColor( mBackgroundColor.r, + mBackgroundColor.g, + mBackgroundColor.b, + mBackgroundColor.a ); + + + // set external pointer so it can be used in calls below + sceneHandler = this; + + + mScreen->addMouseHandler( this ); + mScreen->addKeyboardHandler( this ); + mScreen->addSceneHandler( this ); + mScreen->addRedrawListener( this ); + + + Time::getCurrentTime( &mLastFrameSeconds, &mLastFrameMilliseconds ); + + + + // set up main UI + mMainPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mMainPanelGuiTranslator = new GUITranslatorGL( mMainPanel, mScreen, + gameWidth ); + + guiTranslator = mMainPanelGuiTranslator; + + + mScreen->addSceneHandler( mMainPanelGuiTranslator ); + mScreen->addKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->addMouseHandler( mMainPanelGuiTranslator ); + + mGUIVisible = true; + + + // construct sub-panels, but only add the first one + + mWriteFailedPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + + mDemoCodePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mTitlePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mVolumePanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mSelectPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mWaitPlayerPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mUPNPPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mJoinControllerPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + mLoadingPanel = new GUIPanelGL( 0, 0, gameWidth, gameWidth, + new Color( 0, + 0, + 0, 1.0 ) ); + + + // make sure we can write to our main folder + FILE *testFile = fopen( "testWrite.txt", "w" ); + + if( testFile == NULL ) { + // failed + mMainPanel->add( mWriteFailedPanel ); + mCurrentPanel = mWriteFailedPanel; + } + else { + fclose( testFile ); + + remove( "testWrite.txt" ); + + + if( demoMode ) { + mMainPanel->add( mDemoCodePanel ); + mCurrentPanel = mDemoCodePanel; + } + else { + mMainPanel->add( mTitlePanel ); + mCurrentPanel = mTitlePanel; + } + } + + } + + + +GameSceneHandler::~GameSceneHandler() { + mScreen->removeMouseHandler( this ); + mScreen->removeSceneHandler( this ); + mScreen->removeRedrawListener( this ); + + // remove before deleting, since we're not sure which one is still + // added + mMainPanel->remove( mWriteFailedPanel ); + mMainPanel->remove( mDemoCodePanel ); + mMainPanel->remove( mTitlePanel ); + mMainPanel->remove( mVolumePanel ); + mMainPanel->remove( mSelectPanel ); + mMainPanel->remove( mWaitPlayerPanel ); + mMainPanel->remove( mUPNPPanel ); + mMainPanel->remove( mJoinControllerPanel ); + mMainPanel->remove( mLoadingPanel ); + + + delete mWriteFailedPanel; + delete mDemoCodePanel; + delete mTitlePanel; + delete mVolumePanel; + delete mSelectPanel; + delete mWaitPlayerPanel; + delete mUPNPPanel; + delete mJoinControllerPanel; + delete mLoadingPanel; + + + + + // this will recursively delete all of our selector GUI components + delete mMainPanelGuiTranslator; + + delete mTextGL; + delete mTextGLFixedWidth; + + + } + + + +void GameSceneHandler::setLabelString( LabelGL *inLabel, + char *inTranslationString, + double inScaleFactor ) { + char *labelString = + (char *)TranslationManager::translate( (char*)inTranslationString ); + + inLabel->setText( labelString ); + + TextGL *text = inLabel->getTextGL(); + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + double height = inScaleFactor * 8; + double width = height * strlen( labelString ); + + double actualDrawWidth = + height * text->measureTextWidth( labelString ); + + /* + // round to an integer number of pixels + actualDrawWidth = + round( actualDrawWidth * screenImageWidth ) + / + screenImageWidth; + */ + + double centerW = gameWidth / 2; + //0.5 * (double)( mScreen->getImageWidth() ) / screenImageHeight; + + double guiY = inLabel->getAnchorY(); + + + double labelX = centerW - 0.5 * actualDrawWidth; + + + /* + // round to an integer number of pixels + labelX = + round( labelX * screenImageWidth ) + / + screenImageWidth; + + guiY = + round( guiY * screenImageWidth ) + / + screenImageWidth; + */ + + + inLabel->setPosition( labelX, + guiY, + width, + height ); + } + + + +HighlightLabelGL *GameSceneHandler::createLabel( double inGuiY, + char *inTranslationString, + TextGL *inText ) { + if( inText == NULL ) { + inText = mTextGL; + } + + HighlightLabelGL *returnLabel = + new HighlightLabelGL( 0, inGuiY, 0, 0, "", inText ); + + setLabelString( returnLabel, inTranslationString ); + + return returnLabel; + } + + + + + + +void GameSceneHandler::initFromFiles() { + + // translation language + File languageNameFile( NULL, "language.txt" ); + + if( languageNameFile.exists() ) { + char *languageNameText = languageNameFile.readFileContents(); + + SimpleVector *tokens = tokenizeString( languageNameText ); + + int numTokens = tokens->size(); + + // first token is name + if( numTokens > 0 ) { + char *languageName = *( tokens->getElement( 0 ) ); + + TranslationManager::setLanguage( languageName ); + } + else { + // default + + // TranslationManager already defaults to English, but + // it looks for the language files at runtime before we have set + // the current working directory. + + // Thus, we specify the default again here so that it looks + // for its language files again. + TranslationManager::setLanguage( "English" ); + } + + delete [] languageNameText; + + for( int t=0; tgetElement( t ) ); + } + delete tokens; + } + + + + // load text font + Image *fontImage = readTGA( "font8.tga" ); + + if( fontImage == NULL ) { + // default + // blank font + fontImage = new Image( 256, 256, 4, true ); + } + + mTextGL = new TextGL( fontImage, + // use alpha + true, + // variable character width + false, + // extra space around each character, one pixel + 0.125, + // space is 4 pixels wide (out of 8) + 0.5 ); + largeText = mTextGL; + + mTextGLFixedWidth = new TextGL( fontImage, + // use alpha + true, + // fixed character width + true, + // extra space around each character + 0, + // space is full char width + 1.0 ); + + largeTextFixed = mTextGLFixedWidth; + + delete fontImage; + + + + + // now build gui panels based on labels, which depend on textGL + + mWriteFailedLabel = createLabel( 160, "writeFailed" ); + + // increase scale + setLabelString( mWriteFailedLabel, "writeFailed" ); + + mWriteFailedPanel->add( mWriteFailedLabel ); + + + + + mEnterDemoCodeLabel = createLabel( 175, "enterDemoCode" ); + mDemoCodePanel->add( mEnterDemoCodeLabel ); + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + + double height = 8; + double width = height * demoCodeLength; + + double centerW = gameWidth / 2; + + + char *defaultCode = + SettingsManager::getStringSetting( "demoCode" ); + + char *fieldDefault = ""; + if( defaultCode != NULL ) { + fieldDefault = defaultCode; + } + + mEnterDemoCodeField = new TextFieldGL( centerW - 0.5 * width, + 145, + width, + height, + 1, + fieldDefault, + mTextGLFixedWidth, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + demoCodeLength, + true ); + + mDemoCodePanel->add( mEnterDemoCodeField ); + + + if( demoMode ) { + mEnterDemoCodeField->setEnabled( true ); + mEnterDemoCodeField->setFocus( true ); + mEnterDemoCodeField->lockFocus( true ); + } + + + + if( defaultCode != NULL ) { + + mEnterDemoCodeField->setCursorPosition( strlen( defaultCode ) ); + + + if( demoMode ) { + + // don't destroy this + char *enteredCode = mEnterDemoCodeField->getText(); + + if( strlen( enteredCode ) > 0 ) { + + // run with this code + // disable further input + mEnterDemoCodeField->setEnabled( false ); + mEnterDemoCodeField->setFocus( false ); + mEnterDemoCodeField->lockFocus( false ); + + setLabelString( mEnterDemoCodeLabel, + "checkingCode" ); + + // start + codeChecker = new DemoCodeChecker( enteredCode ); + } + } + + + delete [] defaultCode; + } + + + + + + + mTitleLabel = createLabel( 160, "title" ); + + // increase scale + setLabelString( mTitleLabel, "title" ); + + mTitlePanel->add( mTitleLabel ); + + + + mVolumeLabel = createLabel( 160, "volume" ); + + // increase scale + setLabelString( mVolumeLabel, "volume" ); + + mVolumePanel->add( mVolumeLabel ); + + + mVolumeDoneLabel = createLabel( 48, "volumeDone" ); + + // increase scale + setLabelString( mVolumeDoneLabel, "volumeDone" ); + + mVolumePanel->add( mVolumeDoneLabel ); + + + + Color thumbColor( .5, .5, .5, .5 ); + Color borderColor( .35, .35, .35, .35 ); + + mVolumeSlider = new SliderGL( 160 - 24, 104, + 48, 12, + NULL, 0, + new Color( 0, 0, 0, 1 ), + new Color( 1, 0.5, 0, 1 ), + thumbColor.copy(), + borderColor.copy(), + 1, 4, 1 ); + mVolumePanel->add( mVolumeSlider ); + + // volume starts in middle + // avoid round-off errors + mVolumeSlider->setThumbPosition( (int)(0.5 * 96) / 96.0 ); + + mVolumeSlider->addActionListener( this ); + + + mSelectLabels[0] = + mControlLocalLabel = createLabel( 190, "control_local" ); + + mSelectLabels[1] = + mControlRemoteLabel = createLabel( 160, "control_remote" ); + + mSelectLabels[2] = + mJoinAsPlayerLabel = createLabel( 130, "joinAsPlayer" ); + + mSelectPanel->add( mControlLocalLabel ); + mSelectPanel->add( mControlRemoteLabel ); + mSelectPanel->add( mJoinAsPlayerLabel ); + + mSelectLabels[0]->setHighlight( true ); + mSelectHighlightIndex = 0; + + + mWaitPlayerMessageLabel = createLabel( 205, "" ); + + + + mWaitPlayerLabel = createLabel( 175, "waitingForPlayer" ); + + HostAddress *local = HostAddress::getNumericalLocalAddress(); + + + if( local != NULL ) { + addressString = stringDuplicate( local->mAddressString ); + delete local; + } + else { + addressString = stringDuplicate( "???.???.???.???" ); + } + + + mAddressLabel = createLabel( 145, addressString, + mTextGLFixedWidth ); + + + mDoNotWaitTipLabel = createLabel( 115, "doNotWaitTip" ); + + mWaitPlayerPanel->add( mWaitPlayerMessageLabel ); + mWaitPlayerPanel->add( mWaitPlayerLabel ); + mWaitPlayerPanel->add( mAddressLabel ); + mWaitPlayerPanel->add( mDoNotWaitTipLabel ); + + + + mUPNPLabel = createLabel( 160, "upnpTrying" ); + mUPNPPanel->add( mUPNPLabel ); + + + + mEnterAddressLabel = createLabel( 175, "enterAddress" ); + mJoinControllerPanel->add( mEnterAddressLabel ); + + + mLoadingLabel = createLabel( 160, "loading" ); + + mLoadingPanel->add( mLoadingLabel ); + + + + // 1:1 aspect ratio + // we know font is 8 pixels wide/tall + + height = 8; + width = height * maxAddressLength; + + centerW = gameWidth / 2; + + + char *defaultAddress = + SettingsManager::getStringSetting( "defaultServerAddress" ); + + fieldDefault = ""; + if( defaultAddress != NULL ) { + fieldDefault = defaultAddress; + } + + + mEnterAddressField = new TextFieldGL( centerW - 0.5 * width, + 145, + width, + height, + 1, + fieldDefault, + mTextGLFixedWidth, + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.75, 0.75, 0.75 ), + new Color( 0.15, 0.15, 0.15 ), + new Color( 0.75, 0.75, 0.75 ), + maxAddressLength, + true ); + + mEnterAddressField->setCursorPosition( strlen( fieldDefault ) ); + + mJoinControllerPanel->add( mEnterAddressField ); + + + if( defaultAddress != NULL ) { + delete [] defaultAddress; + } + + + + + + } + + + + + +void GameSceneHandler::drawScene() { + /* + glClearColor( mBackgroundColor->r, + mBackgroundColor->g, + mBackgroundColor->b, + mBackgroundColor->a ); + */ + + glDisable( GL_TEXTURE_2D ); + glDisable( GL_CULL_FACE ); + glDisable( GL_DEPTH_TEST ); + + + /* + // sky + glColor4d( 0.75, 0.75, 0.75, 1.0 ); + + glBegin( GL_QUADS ); { + glVertex2d( -1, 1 ); + glVertex2d( 1, 1 ); + glVertex2d( 1, -1 ); + glVertex2d( -1, -1 ); + } + glEnd(); + */ + } + + + +void GameSceneHandler::mouseMoved( int inX, int inY ) { + } + + + +void GameSceneHandler::mouseDragged( int inX, int inY ) { + } + + + + +void GameSceneHandler::mousePressed( int inX, int inY ) { + } + + + +void GameSceneHandler::mouseReleased( int inX, int inY ) { + // actually, WHY do we let people mouse-click from title? + // rest of menu system is all keyboard-driven + // this sets up a false expectation that the mouse will do something + // in the menus + /* + if( mGUIVisible ) { + + if( mMainPanel->contains( mTitlePanel ) ) { + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mVolumePanel ); + mCurrentPanel = mVolumePanel; + + // test sounds on + for( int i=0; i 6 * lastMillisecondDelta ) { + // limit: this frame represents at most twice the jump of the last + // frame + // printf( "Limiting time jump (requested=%lu ms, last=%lu ms)\n", + // mFrameMillisecondDelta, lastMillisecondDelta ); + + if( mFrameMillisecondDelta > 10000 ) { + AppLog::warning( + "Time between frames more than 10 seconds:\n" ); + // way too big... investigate + AppLog::getLog()->logPrintf( + Log::WARNING_LEVEL, + "Last time = %lu s, %lu ms\n", + mLastFrameSeconds, mLastFrameMilliseconds ); + + Time::getCurrentTime( &mLastFrameSeconds, + &mLastFrameMilliseconds ); + AppLog::getLog()->logPrintf( + Log::WARNING_LEVEL, + "current time = %lu s, %lu ms\n", + mLastFrameSeconds, mLastFrameMilliseconds ); + + } + + mFrameMillisecondDelta = 2 * lastMillisecondDelta; + + } + } + + + // record the time that this frame was drawn + Time::getCurrentTime( &mLastFrameSeconds, &mLastFrameMilliseconds ); + + + // for use with non-constant time-per-frame + // this game is constant time-per-frame + // double frameSecondsDelta = (double)mFrameMillisecondDelta / 1000.0; + + + + mNumFrames ++; + + if( mPrintFrameRate ) { + + if( mNumFrames % mFrameBatchSize == 0 ) { + // finished a batch + + unsigned long timeDelta = + Time::getMillisecondsSince( mFrameBatchStartTimeSeconds, + mFrameBatchStartTimeMilliseconds ); + + double frameRate = + 1000 * (double)mFrameBatchSize / (double)timeDelta; + + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "Frame rate = %f frames/second\n", frameRate ); + + mFrameBatchStartTimeSeconds = mLastFrameSeconds; + mFrameBatchStartTimeMilliseconds = mLastFrameMilliseconds; + } + } + + + // process old connections to clear pending ops and destroy them + for( int i=0; istep() == false ) { + // done + AppLog::info( "Destroying old connection.\n" ); + + delete c; + + oldConnections.deleteElement( i ); + + i--; + } + } + + + + // should we start a game? + if( mMainPanel->contains( mLoadingPanel ) ) { + if( loadingDrawnOnce ) { + + // loading panel drawn at least once + + if( isControllerGame ) { + game = new ControllerGame( mScreen ); + } + else { + // back to default sounds (in case we're Player in a + // v13 game) + setDefaultMusicSounds(); + + game = new PlayerGame( mScreen ); + isControllerGame = false; + } + + if( mGUIVisible ) { + // hide menu gui to start the game + + mMainPanel->remove( mCurrentPanel ); + mCurrentPanel = NULL; + + mScreen->removeSceneHandler( mMainPanelGuiTranslator ); + mScreen->removeKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->removeMouseHandler( mMainPanelGuiTranslator ); + mGUIVisible = false; + + + // add game handlers instead + mScreen->addSceneHandler( game ); + mScreen->addKeyboardHandler( game ); + mScreen->addMouseHandler( game ); + mScreen->addRedrawListener( game ); + + game->setScreen( screen ); + } + } + else { + loadingDrawnOnce = true; + } + + } + + + + + + if( connection != NULL ) { + + if( ! connection->isError() ) { + + char workLeft = connection->step(); + + + if( connection->isConnected() && game == NULL ) { + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + + mMainPanel->remove( mJoinControllerPanel ); + + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + loadingDrawnOnce = false; + + isControllerGame = false; + } + else if( mMainPanel->contains( mWaitPlayerPanel ) ) { + mMainPanel->remove( mWaitPlayerPanel ); + + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + loadingDrawnOnce = false; + + isControllerGame = true; + } + // we construct the game outside the check for a connection + // (based on presence of loading panel) so that same + // construction code can be used even if player hits G + // to start w/out a connection + + /* + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + // got connection as player + + // back to default sounds (in case we're Player in a + // v13 game) + setDefaultMusicSounds(); + + + game = new PlayerGame( mScreen ); + isControllerGame = false; + } + else if( mMainPanel->contains( mWaitPlayerPanel ) ) { + // got connection as controller + game = new ControllerGame( mScreen ); + isControllerGame = true; + } + */ + + + + + // if connected, keep stepping until no network work left + while( workLeft ) { + workLeft = connection->step(); + } + } + + } + else { + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Connection failed: %s\n", connection->getErrorString() ); + + oldConnections.push_back( connection ); + + connection = NULL; + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + + // re-enable input + mEnterAddressField->setEnabled( true ); + mEnterAddressField->setFocus( true ); + mEnterAddressField->lockFocus( true ); + + setLabelString( mEnterAddressLabel, + "joiningFailed" ); + } + } + } + else { + // no connection... is a Controller game already running? + if( game != NULL && isControllerGame ) { + + // are any old connections (which might block hosting on port) + // still waiting to be destroyed? + + if( oldConnections.size() == 0 ) { + + // clear to start a new one + AppLog::info( + "Trying to receive a new Player connection...\n" ); + // start a server + connection = new Connection(); + + game->resetTimer(); + } + } + } + + + + // process old codeCheckers to clear pending ops and destroy them + for( int i=0; istep() == false ) { + // done + AppLog::info( "Destroying old codeChecker.\n" ); + + delete c; + + oldCodeCheckers.deleteElement( i ); + + i--; + } + } + + if( codeChecker != NULL ) { + + if( codeChecker->isError() ) { + if( mMainPanel->contains( mDemoCodePanel ) ) { + + // re-enable input + mEnterDemoCodeField->setEnabled( true ); + mEnterDemoCodeField->setFocus( true ); + mEnterDemoCodeField->lockFocus( true ); + + + char *message = codeChecker->getErrorString(); + + setLabelString( mEnterDemoCodeLabel, message ); + } + oldCodeCheckers.push_back( codeChecker ); + + codeChecker = NULL; + } + else { + + char checkerWorkLeft = codeChecker->step(); + + if( ! checkerWorkLeft + && ! codeChecker->isError() + && codeChecker->codePermitted() ) { + + // will have error if not permitted, which is handled above + + oldCodeCheckers.push_back( codeChecker ); + + codeChecker = NULL; + + if( mMainPanel->contains( mDemoCodePanel ) ) { + // move on to title + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mTitlePanel ); + mCurrentPanel = mTitlePanel; + } + } + } + + } + + + + + + if( game != NULL ) { + game->step(); + + if( game->isQuitting() ) { + + // back out and restore menu gui + if( !mGUIVisible ) { + + mScreen->addSceneHandler( mMainPanelGuiTranslator ); + mScreen->addKeyboardHandler( mMainPanelGuiTranslator ); + mScreen->addMouseHandler( mMainPanelGuiTranslator ); + mGUIVisible = true; + + + + mScreen->removeSceneHandler( game ); + mScreen->removeKeyboardHandler( game ); + mScreen->removeMouseHandler( game ); + mScreen->removeRedrawListener( game ); + } + + delete game; + game = NULL; + + if( connection != NULL ) { + oldConnections.push_back( connection ); + connection = NULL; + } + + // clear music + for( int si=0; siadd( mSelectPanel ); + mCurrentPanel = mSelectPanel; + } + else { + // close UPNP port first + + mMainPanel->add( mUPNPPanel ); + mCurrentPanel = mUPNPPanel; + setLabelString( mUPNPLabel, "upnpCloseTrying" ); + + // wait for it to draw once before blocking + tryUPNPClose = false; + } + } + + } + + + // gradually adjust loudness toward target to avoid clicks + + double trueVolume = getMusicLoudness(); + if( mVolumeTarget != trueVolume ) { + + double newTrueVolume = trueVolume; + + if( mVolumeTarget > trueVolume ) { + newTrueVolume += 0.06; + + if( newTrueVolume > mVolumeTarget ) { + newTrueVolume = mVolumeTarget; + } + } + if( mVolumeTarget < trueVolume ) { + newTrueVolume -= 0.06; + + if( newTrueVolume < mVolumeTarget ) { + newTrueVolume = mVolumeTarget; + } + } + + setMusicLoudness( newTrueVolume ); + } + + + + + // handle auto-starts here + if( autoHostMode && ! mMainPanel->contains( mWaitPlayerPanel ) ) { + // done, don't re-trigger if player backs out of it + autoHostMode = false; + + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + + // start as server + connection = new Connection(); + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mWaitPlayerPanel ); + mCurrentPanel = mWaitPlayerPanel; + } + + if( autoJoinMode && ! mMainPanel->contains( mJoinControllerPanel ) ) { + // done, don't re-trigger if player backs out of it + autoJoinMode = false; + + char *enteredAddress = mEnterAddressField->getText(); + + if( strlen( enteredAddress ) > 0 ) { + // disable further input + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mEnterAddressLabel, + "joiningController" ); + + // start as client + connection = new Connection( enteredAddress ); + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mJoinControllerPanel ); + mCurrentPanel = mJoinControllerPanel; + } + } + + + if( mMainPanel->contains( mUPNPPanel ) && !tryUPNP && !upnpPortOpen ) { + // try blocking op on next frame, so that panel can draw at least once + tryUPNP = true; + } + else if( tryUPNP ) { + tryUPNP = false; + + char *externalIP; + + int result = mapPort( Connection::getPort(), "Sleep Is Death", + 2000, + &externalIP ); + + if( result == 1 ) { + upnpPortOpen = true; + if( externalIP != NULL ) { + setLabelString( mAddressLabel, externalIP ); + + delete [] addressString; + addressString = externalIP; + } + } + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mWaitPlayerPanel ); + mCurrentPanel = mWaitPlayerPanel; + + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + + // start as server + connection = new Connection(); + + + if( result == 1 ) { + setLabelString( mWaitPlayerMessageLabel, "upnpSuccess" ); + } + else { + setLabelString( mWaitPlayerMessageLabel, "upnpFailure" ); + } + } + + + if( mMainPanel->contains( mUPNPPanel ) && upnpPortOpen && !tryUPNPClose ) { + // try blocking op on next frame, so that panel can draw at least once + tryUPNPClose = true; + } + else if( tryUPNPClose ) { + tryUPNPClose = false; + + int result = unmapPort( Connection::getPort(), 2000 ); + + if( result == 1 ) { + upnpPortOpen = false; + } + + if( game == NULL ) { + // not in the middle of a game, back out to select panel + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + + + // back to local address + HostAddress *local = HostAddress::getNumericalLocalAddress(); + + + if( local != NULL ) { + delete [] addressString; + + addressString = stringDuplicate( local->mAddressString ); + + setLabelString( mAddressLabel, addressString ); + + delete local; + } + + } + else { + // quitting, but we just closed the UPNP port first + exit( 0 ); + } + } + + } + + + +static unsigned char lastKeyPressed = '\0'; + + +void GameSceneHandler::keyPressed( + unsigned char inKey, int inX, int inY ) { + + if( enableSlowdownKeys ) { + + if( inKey == '^' ) { + // slow + mMaxFrameRate = 2; + } + if( inKey == '&' ) { + // normal + mMaxFrameRate = 30; + } + } + + + if( mGUIVisible ) { + + if( mMainPanel->contains( mTitlePanel ) || + mMainPanel->contains( mVolumePanel ) ) { + if( !hardToQuitMode ) { + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + exit( 0 ); + } + } + else { + // # followed by ESC + if( lastKeyPressed == '#' && inKey == 27 ) { + exit( 0 ); + } + lastKeyPressed = inKey; + } + + if( inKey != 'q' && inKey != 'Q' && inKey != 27 && inKey != '#' ) { + // any other key advances to next screen + if( mMainPanel->contains( mTitlePanel ) ) { + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mVolumePanel ); + mCurrentPanel = mVolumePanel; + + // test sounds on + // emulate old 1-grid test run (rising tones) + for( int i=0; icontains( mVolumePanel ) ) { + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + + // test sounds off + for( int i=0; icontains( mSelectPanel ) ) { + + if( !hardToQuitMode ) { + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + exit( 0 ); + } + } + else { + // # followed by ESC + if( lastKeyPressed == '#' && inKey == 27 ) { + exit( 0 ); + } + lastKeyPressed = inKey; + } + + if( inKey != 'q' && inKey != 'Q' && inKey != 27 && inKey != '#' ) { + // any other key press triggers selection + + GUIPanelGL *panelToShow = NULL; + + switch( mSelectHighlightIndex ) { + case 0: + panelToShow = mWaitPlayerPanel; + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + setLabelString( mWaitPlayerLabel, "waitingForPlayer" ); + setLabelString( mWaitPlayerMessageLabel, "" ); + + // start as server + connection = new Connection(); + + break; + case 1: + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + if( upnpPortOpen ) { + // already open, maybe from failed close before? + + panelToShow = mWaitPlayerPanel; + + setLabelString( mWaitPlayerLabel, + "waitingForPlayer" ); + setLabelString( mWaitPlayerMessageLabel, "" ); + + // start as server + connection = new Connection(); + } + else { + // open it + panelToShow = mUPNPPanel; + + + setLabelString( mUPNPLabel, "upnpTrying" ); + + // wait until panel draws at least once + tryUPNP = false; + } + break; + case 2: + panelToShow = mJoinControllerPanel; + + setLabelString( mEnterAddressLabel, "enterAddress" ); + mEnterAddressField->setFocus( true ); + mEnterAddressField->lockFocus( true ); + mEnterAddressField->setEnabled( true ); + + // don't pass this key press through to field + mEnterAddressField->ignoreNextKey(); + break; + } + + if( panelToShow != NULL ) { + mMainPanel->remove( mSelectPanel ); + mMainPanel->add( panelToShow ); + mCurrentPanel = panelToShow; + } + } + } + else { + if( mMainPanel->contains( mWriteFailedPanel ) ) { + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + // q or escape + exit( 0 ); + } + } + + + if( mMainPanel->contains( mDemoCodePanel ) ) { + // look for enter + if( inKey == 13 && mEnterDemoCodeField->isFocused() ) { + + // don't destroy this + char *enteredCode = mEnterDemoCodeField->getText(); + + if( strlen( enteredCode ) > 0 ) { + // disable further input + mEnterDemoCodeField->setEnabled( false ); + mEnterDemoCodeField->setFocus( false ); + mEnterDemoCodeField->lockFocus( false ); + + setLabelString( mEnterDemoCodeLabel, + "checkingCode" ); + + // save this for next time + SettingsManager::setSetting( "demoCode", + enteredCode ); + + // start + codeChecker = new DemoCodeChecker( enteredCode ); + } + } + else if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + // q or escape + exit( 0 ); + } + } + + + if( mMainPanel->contains( mJoinControllerPanel ) ) { + // look for enter + if( inKey == 13 && mEnterAddressField->isFocused() ) { + + // don't destroy this + char *enteredAddress = mEnterAddressField->getText(); + + if( strlen( enteredAddress ) > 0 ) { + // disable further input + mEnterAddressField->setFocus( false ); + mEnterAddressField->lockFocus( false ); + mEnterAddressField->setEnabled( false ); + + + setLabelString( mEnterAddressLabel, + "joiningController" ); + + // start as client + connection = new Connection( enteredAddress ); + } + } + } + + if( mMainPanel->contains( mWaitPlayerPanel ) ) { + // look for "g" to go ahaead + if( inKey == 'g' || inKey == 'G' ) { + + mMainPanel->remove( mCurrentPanel ); + mMainPanel->add( mLoadingPanel ); + mCurrentPanel = mLoadingPanel; + isControllerGame = true; + loadingDrawnOnce = false; + + /* + game = new ControllerGame( mScreen ); + isControllerGame = true; + + // hide menu gui to start the game + + mMainPanel->remove( mCurrentPanel ); + mCurrentPanel = NULL; + + mScreen->removeSceneHandler( mMainPanelGuiTranslator ); + mScreen->removeKeyboardHandler( mMainPanelGuiTranslator ); + mGUIVisible = false; + + + // add game handlers instead + mScreen->addSceneHandler( game ); + mScreen->addKeyboardHandler( game ); + mScreen->addMouseHandler( game ); + mScreen->addRedrawListener( game ); + + game->setScreen( screen ); + */ + } + } + + + + // q or escape pressed in any sub-panel + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + + // back up + + // wait for this connection to finish before destroying it + + if( connection != NULL ) { + oldConnections.push_back( connection ); + connection = NULL; + } + + mMainPanel->remove( mCurrentPanel ); + + if( upnpPortOpen && mCurrentPanel == mWaitPlayerPanel ) { + // close upnp port first + AppLog::info( "Setting upnp panel\n" ); + mMainPanel->add( mUPNPPanel ); + mCurrentPanel = mUPNPPanel; + + setLabelString( mUPNPLabel, "upnpCloseTrying" ); + + // show upnp panel at least once before blocking + tryUPNPClose = false; + } + else { + mMainPanel->add( mSelectPanel ); + mCurrentPanel = mSelectPanel; + } + + } + } + + + } + + + + /* + // q or escape + if( inKey == 'q' || inKey == 'Q' || inKey == 27 ) { + if( !showingQuitQuestion ) { + oldStatusText = stringDuplicate( mStatusLabel->getText() ); + + showingQuitQuestion = true; + + setLabelString( mStatusLabel, "quitQuestion" ); + } + } + // answer to quit question? + else if( showingQuitQuestion && ( inKey == 'y' || inKey == 'Y' ) ) { + delete [] oldStatusText; + oldStatusText = NULL; + + exit( 0 ); + } + else if( showingQuitQuestion && ( inKey == 'n' || inKey == 'N' ) ) { + setLabelString( mStatusLabel, oldStatusText ); + + delete [] oldStatusText; + oldStatusText = NULL; + + showingQuitQuestion = false; + } + */ + } + + + +void GameSceneHandler::keyReleased( + unsigned char inKey, int inX, int inY ) { + + } + + + +void GameSceneHandler::specialKeyPressed( + int inKey, int inX, int inY ) { + + /* + // keep key states for later, whether we act on them now or not + switch( inKey ) { + case MG_KEY_LEFT: + mLeftKeyDown = true; + break; + case MG_KEY_RIGHT: + mRightKeyDown = true; + break; + } + */ + + + switch( inKey ) { + case MG_KEY_UP: + if( mGUIVisible && + mMainPanel->contains( mSelectPanel ) ) { + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( false ); + + mSelectHighlightIndex --; + if( mSelectHighlightIndex < 0 ) { + mSelectHighlightIndex = 2; + } + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( true ); + } + break; + case MG_KEY_DOWN: + if( mGUIVisible && + mMainPanel->contains( mSelectPanel ) ) { + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( false ); + + mSelectHighlightIndex ++; + if( mSelectHighlightIndex > 2 ) { + mSelectHighlightIndex = 0; + } + mSelectLabels[ mSelectHighlightIndex ]->setHighlight( true ); + } + break; + } + + } + + + +void GameSceneHandler::specialKeyReleased( + int inKey, int inX, int inY ) { + + /* + // keep key states for later, whether we act on them now or not + switch( inKey ) { + case MG_KEY_LEFT: + mLeftKeyDown = false; + break; + case MG_KEY_RIGHT: + mRightKeyDown = false; + break; + } + */ + } + + + +void GameSceneHandler::actionPerformed( GUIComponent *inTarget ) { + + if( inTarget == mVolumeSlider ) { + mVolumeTarget = mVolumeSlider->getThumbPosition(); + } + } + + + + + diff --git a/gameSource/glow.png b/gameSource/glow.png new file mode 100644 index 0000000..48aadb2 Binary files /dev/null and b/gameSource/glow.png differ diff --git a/gameSource/graph.txt b/gameSource/graph.txt new file mode 100644 index 0000000..ab7cfac --- /dev/null +++ b/gameSource/graph.txt @@ -0,0 +1,6 @@ +# command: dot -Tpng graph.txt >graph.png + +digraph G{ + +n135032920; n135032920 [label = "a () {135032920}"]; n135032976; n135032976 [label = "p () {135032976}"]; n135034496; n135034496 [label = "a () {135034496}"]; n135034536; n135034536 [label = "r () {135034536}"]; n135034576; n135034576 [label = "o () {135034576}"]; n135034632; n135034632 [label = "n (aaron, ) {135034632}"]; n135034576 -> n135034632 [label = "D", weight=4]; n135034536 -> n135034576 [label = "D", weight=4]; n135034496 -> n135034536 [label = "D", weight=4]; n135032976 -> n135034496 [label = "L", weight=1]; n135033032; n135033032 [label = "p () {135033032}"]; n135033808; n135033808 [label = "r () {135033808}"]; n135033848; n135033848 [label = "o () {135033848}"]; n135033888; n135033888 [label = "n (apron, ) {135033888}"]; n135033848 -> n135033888 [label = "D", weight=4]; n135033808 -> n135033848 [label = "D", weight=4]; n135033032 -> n135033808 [label = "R", weight=1]; n135032976 -> n135033032 [label = "D", weight=4]; n135034704; n135034704 [label = "r () {135034704}"]; n135034760; n135034760 [label = "o () {135034760}"]; n135034816; n135034816 [label = "n (aaron, ) {135034816}"]; n135034760 -> n135034816 [label = "D", weight=4]; n135034704 -> n135034760 [label = "D", weight=4]; n135032976 -> n135034704 [label = "R", weight=1]; n135032920 -> n135032976 [label = "D", weight=4]; n135033216; n135033216 [label = "p () {135033216}"]; n135033552; n135033552 [label = "l () {135033552}"]; n135034296; n135034296 [label = "o (no, ) {135034296}"]; n135034408; n135034408 [label = "n (apron, aaron, ) {135034408}"]; n135034928; n135034928 [label = "o (no, ) {135034928}"]; n135034408 -> n135034928 [label = "D", weight=4]; n135034296 -> n135034408 [label = "L", weight=1]; n135034352; n135034352 [label = "n (apron, aaron, ) {135034352}"]; n135034296 -> n135034352 [label = "D", weight=4]; n135033552 -> n135034296 [label = "R", weight=1]; n135033216 -> n135033552 [label = "L", weight=1]; n135033272; n135033272 [label = "p () {135033272}"]; n135033960; n135033960 [label = "r () {135033960}"]; n135034016; n135034016 [label = "o () {135034016}"]; n135034072; n135034072 [label = "n (apron, ) {135034072}"]; n135034016 -> n135034072 [label = "D", weight=4]; n135033960 -> n135034016 [label = "D", weight=4]; n135033272 -> n135033960 [label = "R", weight=1]; n135033216 -> n135033272 [label = "D", weight=4]; n135034128; n135034128 [label = "r () {135034128}"]; n135034184; n135034184 [label = "o () {135034184}"]; n135034240; n135034240 [label = "n (apron, aaron, ) {135034240}"]; n135034184 -> n135034240 [label = "D", weight=4]; n135034128 -> n135034184 [label = "D", weight=4]; n135033216 -> n135034128 [label = "R", weight=1]; n135032920 -> n135033216 [label = "R", weight=1]; +} \ No newline at end of file diff --git a/gameSource/graphics/act.tga b/gameSource/graphics/act.tga new file mode 100644 index 0000000..93b017b Binary files /dev/null and b/gameSource/graphics/act.tga differ diff --git a/gameSource/graphics/actionBoxEnd.tga b/gameSource/graphics/actionBoxEnd.tga new file mode 100644 index 0000000..1f3e44c Binary files /dev/null and b/gameSource/graphics/actionBoxEnd.tga differ diff --git a/gameSource/graphics/actionBoxEndFlip.tga b/gameSource/graphics/actionBoxEndFlip.tga new file mode 100644 index 0000000..66a6058 Binary files /dev/null and b/gameSource/graphics/actionBoxEndFlip.tga differ diff --git a/gameSource/graphics/actionBoxEndFlipTall.tga b/gameSource/graphics/actionBoxEndFlipTall.tga new file mode 100644 index 0000000..2e4ad8d Binary files /dev/null and b/gameSource/graphics/actionBoxEndFlipTall.tga differ diff --git a/gameSource/graphics/actionBoxEndTall.tga b/gameSource/graphics/actionBoxEndTall.tga new file mode 100644 index 0000000..da9a103 Binary files /dev/null and b/gameSource/graphics/actionBoxEndTall.tga differ diff --git a/gameSource/graphics/actionBoxMiddle.tga b/gameSource/graphics/actionBoxMiddle.tga new file mode 100644 index 0000000..5ad9327 Binary files /dev/null and b/gameSource/graphics/actionBoxMiddle.tga differ diff --git a/gameSource/graphics/actionBoxMiddleExtra.tga b/gameSource/graphics/actionBoxMiddleExtra.tga new file mode 100644 index 0000000..abe755c Binary files /dev/null and b/gameSource/graphics/actionBoxMiddleExtra.tga differ diff --git a/gameSource/graphics/actionBoxMiddleExtraTall.tga b/gameSource/graphics/actionBoxMiddleExtraTall.tga new file mode 100644 index 0000000..1b1d847 Binary files /dev/null and b/gameSource/graphics/actionBoxMiddleExtraTall.tga differ diff --git a/gameSource/graphics/actionBoxMiddleTall.tga b/gameSource/graphics/actionBoxMiddleTall.tga new file mode 100644 index 0000000..f90e0bf Binary files /dev/null and b/gameSource/graphics/actionBoxMiddleTall.tga differ diff --git a/gameSource/graphics/actionBoxStart.tga b/gameSource/graphics/actionBoxStart.tga new file mode 100644 index 0000000..150cfb7 Binary files /dev/null and b/gameSource/graphics/actionBoxStart.tga differ diff --git a/gameSource/graphics/actionBoxStartTall.tga b/gameSource/graphics/actionBoxStartTall.tga new file mode 100644 index 0000000..71f1078 Binary files /dev/null and b/gameSource/graphics/actionBoxStartTall.tga differ diff --git a/gameSource/graphics/addObject.tga b/gameSource/graphics/addObject.tga new file mode 100644 index 0000000..9f9b0fe Binary files /dev/null and b/gameSource/graphics/addObject.tga differ diff --git a/gameSource/graphics/anchor.tga b/gameSource/graphics/anchor.tga new file mode 100644 index 0000000..952055c Binary files /dev/null and b/gameSource/graphics/anchor.tga differ diff --git a/gameSource/graphics/bubbleBottom.tga b/gameSource/graphics/bubbleBottom.tga new file mode 100644 index 0000000..899b5a4 Binary files /dev/null and b/gameSource/graphics/bubbleBottom.tga differ diff --git a/gameSource/graphics/bubbleBottomFlip.tga b/gameSource/graphics/bubbleBottomFlip.tga new file mode 100644 index 0000000..a6d5b6b Binary files /dev/null and b/gameSource/graphics/bubbleBottomFlip.tga differ diff --git a/gameSource/graphics/bubbleBottomNoTail.tga b/gameSource/graphics/bubbleBottomNoTail.tga new file mode 100644 index 0000000..adea7bd Binary files /dev/null and b/gameSource/graphics/bubbleBottomNoTail.tga differ diff --git a/gameSource/graphics/bubbleMiddle.tga b/gameSource/graphics/bubbleMiddle.tga new file mode 100644 index 0000000..b9eeabc Binary files /dev/null and b/gameSource/graphics/bubbleMiddle.tga differ diff --git a/gameSource/graphics/bubbleMiddleExtra.tga b/gameSource/graphics/bubbleMiddleExtra.tga new file mode 100644 index 0000000..70b95bb Binary files /dev/null and b/gameSource/graphics/bubbleMiddleExtra.tga differ diff --git a/gameSource/graphics/bubbleMiddleExtraThin.tga b/gameSource/graphics/bubbleMiddleExtraThin.tga new file mode 100644 index 0000000..3e10775 Binary files /dev/null and b/gameSource/graphics/bubbleMiddleExtraThin.tga differ diff --git a/gameSource/graphics/bubbleMiddleTail.tga b/gameSource/graphics/bubbleMiddleTail.tga new file mode 100644 index 0000000..a3c2182 Binary files /dev/null and b/gameSource/graphics/bubbleMiddleTail.tga differ diff --git a/gameSource/graphics/bubbleMiddleTailFlip.tga b/gameSource/graphics/bubbleMiddleTailFlip.tga new file mode 100644 index 0000000..2126c6c Binary files /dev/null and b/gameSource/graphics/bubbleMiddleTailFlip.tga differ diff --git a/gameSource/graphics/bubbleTop.tga b/gameSource/graphics/bubbleTop.tga new file mode 100644 index 0000000..7673273 Binary files /dev/null and b/gameSource/graphics/bubbleTop.tga differ diff --git a/gameSource/graphics/canDrop.tga b/gameSource/graphics/canDrop.tga new file mode 100644 index 0000000..2cd28bb Binary files /dev/null and b/gameSource/graphics/canDrop.tga differ diff --git a/gameSource/graphics/clear.tga b/gameSource/graphics/clear.tga new file mode 100644 index 0000000..fc573bf Binary files /dev/null and b/gameSource/graphics/clear.tga differ diff --git a/gameSource/graphics/close.tga b/gameSource/graphics/close.tga new file mode 100644 index 0000000..c354050 Binary files /dev/null and b/gameSource/graphics/close.tga differ diff --git a/gameSource/graphics/colorSpot.tga b/gameSource/graphics/colorSpot.tga new file mode 100644 index 0000000..f4d7f1c Binary files /dev/null and b/gameSource/graphics/colorSpot.tga differ diff --git a/gameSource/graphics/colorize.tga b/gameSource/graphics/colorize.tga new file mode 100644 index 0000000..0985d73 Binary files /dev/null and b/gameSource/graphics/colorize.tga differ diff --git a/gameSource/graphics/confirm.tga b/gameSource/graphics/confirm.tga new file mode 100644 index 0000000..d154e55 Binary files /dev/null and b/gameSource/graphics/confirm.tga differ diff --git a/gameSource/graphics/delete.tga b/gameSource/graphics/delete.tga new file mode 100644 index 0000000..98207f7 Binary files /dev/null and b/gameSource/graphics/delete.tga differ diff --git a/gameSource/graphics/edit.tga b/gameSource/graphics/edit.tga new file mode 100644 index 0000000..3628183 Binary files /dev/null and b/gameSource/graphics/edit.tga differ diff --git a/gameSource/graphics/editObject.tga b/gameSource/graphics/editObject.tga new file mode 100644 index 0000000..bf6ae30 Binary files /dev/null and b/gameSource/graphics/editObject.tga differ diff --git a/gameSource/graphics/editRoom.tga b/gameSource/graphics/editRoom.tga new file mode 100644 index 0000000..3c71db4 Binary files /dev/null and b/gameSource/graphics/editRoom.tga differ diff --git a/gameSource/graphics/editScale.tga b/gameSource/graphics/editScale.tga new file mode 100644 index 0000000..b9dfffd Binary files /dev/null and b/gameSource/graphics/editScale.tga differ diff --git a/gameSource/graphics/editSprite.tga b/gameSource/graphics/editSprite.tga new file mode 100644 index 0000000..a1a91fa Binary files /dev/null and b/gameSource/graphics/editSprite.tga differ diff --git a/gameSource/graphics/emptyPhrase.tga b/gameSource/graphics/emptyPhrase.tga new file mode 100644 index 0000000..a70a872 Binary files /dev/null and b/gameSource/graphics/emptyPhrase.tga differ diff --git a/gameSource/graphics/erase.tga b/gameSource/graphics/erase.tga new file mode 100644 index 0000000..13a0dab Binary files /dev/null and b/gameSource/graphics/erase.tga differ diff --git a/gameSource/graphics/fast.tga b/gameSource/graphics/fast.tga new file mode 100644 index 0000000..3d4f4f5 Binary files /dev/null and b/gameSource/graphics/fast.tga differ diff --git a/gameSource/graphics/fill.tga b/gameSource/graphics/fill.tga new file mode 100644 index 0000000..c9cbd30 Binary files /dev/null and b/gameSource/graphics/fill.tga differ diff --git a/gameSource/graphics/first.tga b/gameSource/graphics/first.tga new file mode 100644 index 0000000..0cfe5b9 Binary files /dev/null and b/gameSource/graphics/first.tga differ diff --git a/gameSource/graphics/flipBook.tga b/gameSource/graphics/flipBook.tga new file mode 100644 index 0000000..911dece Binary files /dev/null and b/gameSource/graphics/flipBook.tga differ diff --git a/gameSource/graphics/flipH.tga b/gameSource/graphics/flipH.tga new file mode 100644 index 0000000..dcb1f06 Binary files /dev/null and b/gameSource/graphics/flipH.tga differ diff --git a/gameSource/graphics/flipV.tga b/gameSource/graphics/flipV.tga new file mode 100644 index 0000000..cc7f10f Binary files /dev/null and b/gameSource/graphics/flipV.tga differ diff --git a/gameSource/graphics/font_8_16.tga b/gameSource/graphics/font_8_16.tga new file mode 100644 index 0000000..33a70e5 Binary files /dev/null and b/gameSource/graphics/font_8_16.tga differ diff --git a/gameSource/graphics/freeze.tga b/gameSource/graphics/freeze.tga new file mode 100644 index 0000000..e6d70aa Binary files /dev/null and b/gameSource/graphics/freeze.tga differ diff --git a/gameSource/graphics/glow.tga b/gameSource/graphics/glow.tga new file mode 100644 index 0000000..fca9acc Binary files /dev/null and b/gameSource/graphics/glow.tga differ diff --git a/gameSource/graphics/grid.tga b/gameSource/graphics/grid.tga new file mode 100644 index 0000000..08270fc Binary files /dev/null and b/gameSource/graphics/grid.tga differ diff --git a/gameSource/graphics/hints.tga b/gameSource/graphics/hints.tga new file mode 100644 index 0000000..1ab7fd1 Binary files /dev/null and b/gameSource/graphics/hints.tga differ diff --git a/gameSource/graphics/hold.tga b/gameSource/graphics/hold.tga new file mode 100644 index 0000000..d775792 Binary files /dev/null and b/gameSource/graphics/hold.tga differ diff --git a/gameSource/graphics/horLine.tga b/gameSource/graphics/horLine.tga new file mode 100644 index 0000000..e0e194e Binary files /dev/null and b/gameSource/graphics/horLine.tga differ diff --git a/gameSource/graphics/last.tga b/gameSource/graphics/last.tga new file mode 100644 index 0000000..2bf59e6 Binary files /dev/null and b/gameSource/graphics/last.tga differ diff --git a/gameSource/graphics/layerDown.tga b/gameSource/graphics/layerDown.tga new file mode 100644 index 0000000..d9da322 Binary files /dev/null and b/gameSource/graphics/layerDown.tga differ diff --git a/gameSource/graphics/layerGrab.tga b/gameSource/graphics/layerGrab.tga new file mode 100644 index 0000000..8f1d2e7 Binary files /dev/null and b/gameSource/graphics/layerGrab.tga differ diff --git a/gameSource/graphics/layerReplace.tga b/gameSource/graphics/layerReplace.tga new file mode 100644 index 0000000..bf52498 Binary files /dev/null and b/gameSource/graphics/layerReplace.tga differ diff --git a/gameSource/graphics/layerUp.tga b/gameSource/graphics/layerUp.tga new file mode 100644 index 0000000..6069efe Binary files /dev/null and b/gameSource/graphics/layerUp.tga differ diff --git a/gameSource/graphics/left.tga b/gameSource/graphics/left.tga new file mode 100644 index 0000000..299b75c Binary files /dev/null and b/gameSource/graphics/left.tga differ diff --git a/gameSource/graphics/lock.tga b/gameSource/graphics/lock.tga new file mode 100644 index 0000000..6604006 Binary files /dev/null and b/gameSource/graphics/lock.tga differ diff --git a/gameSource/graphics/lockSelected.tga b/gameSource/graphics/lockSelected.tga new file mode 100644 index 0000000..46393c3 Binary files /dev/null and b/gameSource/graphics/lockSelected.tga differ diff --git a/gameSource/graphics/move.tga b/gameSource/graphics/move.tga new file mode 100644 index 0000000..7e1a1f8 Binary files /dev/null and b/gameSource/graphics/move.tga differ diff --git a/gameSource/graphics/music.tga b/gameSource/graphics/music.tga new file mode 100644 index 0000000..d8b44da Binary files /dev/null and b/gameSource/graphics/music.tga differ diff --git a/gameSource/graphics/noDrop.tga b/gameSource/graphics/noDrop.tga new file mode 100644 index 0000000..22aca04 Binary files /dev/null and b/gameSource/graphics/noDrop.tga differ diff --git a/gameSource/graphics/noGlow.tga b/gameSource/graphics/noGlow.tga new file mode 100644 index 0000000..4389902 Binary files /dev/null and b/gameSource/graphics/noGlow.tga differ diff --git a/gameSource/graphics/normalSpeed.tga b/gameSource/graphics/normalSpeed.tga new file mode 100644 index 0000000..23e7865 Binary files /dev/null and b/gameSource/graphics/normalSpeed.tga differ diff --git a/gameSource/graphics/note.tga b/gameSource/graphics/note.tga new file mode 100644 index 0000000..3753b2c Binary files /dev/null and b/gameSource/graphics/note.tga differ diff --git a/gameSource/graphics/noteSpace.tga b/gameSource/graphics/noteSpace.tga new file mode 100644 index 0000000..8b27c4f Binary files /dev/null and b/gameSource/graphics/noteSpace.tga differ diff --git a/gameSource/graphics/octave.tga b/gameSource/graphics/octave.tga new file mode 100644 index 0000000..1ef3f25 Binary files /dev/null and b/gameSource/graphics/octave.tga differ diff --git a/gameSource/graphics/pack.tga b/gameSource/graphics/pack.tga new file mode 100644 index 0000000..018859f Binary files /dev/null and b/gameSource/graphics/pack.tga differ diff --git a/gameSource/graphics/packAlreadyIn.tga b/gameSource/graphics/packAlreadyIn.tga new file mode 100644 index 0000000..d2f7ad2 Binary files /dev/null and b/gameSource/graphics/packAlreadyIn.tga differ diff --git a/gameSource/graphics/packSave.tga b/gameSource/graphics/packSave.tga new file mode 100644 index 0000000..4ac094b Binary files /dev/null and b/gameSource/graphics/packSave.tga differ diff --git a/gameSource/graphics/pen.tga b/gameSource/graphics/pen.tga new file mode 100644 index 0000000..9940d36 Binary files /dev/null and b/gameSource/graphics/pen.tga differ diff --git a/gameSource/graphics/pickColor.tga b/gameSource/graphics/pickColor.tga new file mode 100644 index 0000000..41de2b7 Binary files /dev/null and b/gameSource/graphics/pickColor.tga differ diff --git a/gameSource/graphics/playerAnchor.tga b/gameSource/graphics/playerAnchor.tga new file mode 100644 index 0000000..8c3dad6 Binary files /dev/null and b/gameSource/graphics/playerAnchor.tga differ diff --git a/gameSource/graphics/practice.tga b/gameSource/graphics/practice.tga new file mode 100644 index 0000000..d8e7e75 Binary files /dev/null and b/gameSource/graphics/practice.tga differ diff --git a/gameSource/graphics/practiceStop.tga b/gameSource/graphics/practiceStop.tga new file mode 100644 index 0000000..764ca6d Binary files /dev/null and b/gameSource/graphics/practiceStop.tga differ diff --git a/gameSource/graphics/redo.tga b/gameSource/graphics/redo.tga new file mode 100644 index 0000000..642e089 Binary files /dev/null and b/gameSource/graphics/redo.tga differ diff --git a/gameSource/graphics/removeObject.tga b/gameSource/graphics/removeObject.tga new file mode 100644 index 0000000..cc1a742 Binary files /dev/null and b/gameSource/graphics/removeObject.tga differ diff --git a/gameSource/graphics/removeSprite.tga b/gameSource/graphics/removeSprite.tga new file mode 100644 index 0000000..17d9af6 Binary files /dev/null and b/gameSource/graphics/removeSprite.tga differ diff --git a/gameSource/graphics/replaceSprite.tga b/gameSource/graphics/replaceSprite.tga new file mode 100644 index 0000000..4ea5c99 Binary files /dev/null and b/gameSource/graphics/replaceSprite.tga differ diff --git a/gameSource/graphics/right.tga b/gameSource/graphics/right.tga new file mode 100644 index 0000000..34129ff Binary files /dev/null and b/gameSource/graphics/right.tga differ diff --git a/gameSource/graphics/rotateCCW.tga b/gameSource/graphics/rotateCCW.tga new file mode 100644 index 0000000..66d00a8 Binary files /dev/null and b/gameSource/graphics/rotateCCW.tga differ diff --git a/gameSource/graphics/rotateCW.tga b/gameSource/graphics/rotateCW.tga new file mode 100644 index 0000000..73ce866 Binary files /dev/null and b/gameSource/graphics/rotateCW.tga differ diff --git a/gameSource/graphics/scaleSpace.tga b/gameSource/graphics/scaleSpace.tga new file mode 100644 index 0000000..83fa6db Binary files /dev/null and b/gameSource/graphics/scaleSpace.tga differ diff --git a/gameSource/graphics/search.tga b/gameSource/graphics/search.tga new file mode 100644 index 0000000..85ba8d7 Binary files /dev/null and b/gameSource/graphics/search.tga differ diff --git a/gameSource/graphics/selection.tga b/gameSource/graphics/selection.tga new file mode 100644 index 0000000..28b6ec8 Binary files /dev/null and b/gameSource/graphics/selection.tga differ diff --git a/gameSource/graphics/send.tga b/gameSource/graphics/send.tga new file mode 100644 index 0000000..d583baf Binary files /dev/null and b/gameSource/graphics/send.tga differ diff --git a/gameSource/graphics/sendConfirm.tga b/gameSource/graphics/sendConfirm.tga new file mode 100644 index 0000000..0498264 Binary files /dev/null and b/gameSource/graphics/sendConfirm.tga differ diff --git a/gameSource/graphics/setObject.tga b/gameSource/graphics/setObject.tga new file mode 100644 index 0000000..7a8bae7 Binary files /dev/null and b/gameSource/graphics/setObject.tga differ diff --git a/gameSource/graphics/slow.tga b/gameSource/graphics/slow.tga new file mode 100644 index 0000000..73b0fd7 Binary files /dev/null and b/gameSource/graphics/slow.tga differ diff --git a/gameSource/graphics/smallAdd.tga b/gameSource/graphics/smallAdd.tga new file mode 100644 index 0000000..99ebf6e Binary files /dev/null and b/gameSource/graphics/smallAdd.tga differ diff --git a/gameSource/graphics/speak.tga b/gameSource/graphics/speak.tga new file mode 100644 index 0000000..779be59 Binary files /dev/null and b/gameSource/graphics/speak.tga differ diff --git a/gameSource/graphics/speechBox.tga b/gameSource/graphics/speechBox.tga new file mode 100644 index 0000000..18f079e Binary files /dev/null and b/gameSource/graphics/speechBox.tga differ diff --git a/gameSource/graphics/speechBoxBottom.tga b/gameSource/graphics/speechBoxBottom.tga new file mode 100644 index 0000000..787270d Binary files /dev/null and b/gameSource/graphics/speechBoxBottom.tga differ diff --git a/gameSource/graphics/speechBoxTop.tga b/gameSource/graphics/speechBoxTop.tga new file mode 100644 index 0000000..eb49d90 Binary files /dev/null and b/gameSource/graphics/speechBoxTop.tga differ diff --git a/gameSource/graphics/speechBubble.tga b/gameSource/graphics/speechBubble.tga new file mode 100644 index 0000000..a3456a1 Binary files /dev/null and b/gameSource/graphics/speechBubble.tga differ diff --git a/gameSource/graphics/stack.tga b/gameSource/graphics/stack.tga new file mode 100644 index 0000000..c47e286 Binary files /dev/null and b/gameSource/graphics/stack.tga differ diff --git a/gameSource/graphics/stamp.tga b/gameSource/graphics/stamp.tga new file mode 100644 index 0000000..758eec1 Binary files /dev/null and b/gameSource/graphics/stamp.tga differ diff --git a/gameSource/graphics/timeMark.tga b/gameSource/graphics/timeMark.tga new file mode 100644 index 0000000..d23a808 Binary files /dev/null and b/gameSource/graphics/timeMark.tga differ diff --git a/gameSource/graphics/trans.tga b/gameSource/graphics/trans.tga new file mode 100644 index 0000000..3110b52 Binary files /dev/null and b/gameSource/graphics/trans.tga differ diff --git a/gameSource/graphics/transSmall.tga b/gameSource/graphics/transSmall.tga new file mode 100644 index 0000000..cc1f922 Binary files /dev/null and b/gameSource/graphics/transSmall.tga differ diff --git a/gameSource/graphics/undo.tga b/gameSource/graphics/undo.tga new file mode 100644 index 0000000..1802fb8 Binary files /dev/null and b/gameSource/graphics/undo.tga differ diff --git a/gameSource/graphics/unzoom.tga b/gameSource/graphics/unzoom.tga new file mode 100644 index 0000000..419f344 Binary files /dev/null and b/gameSource/graphics/unzoom.tga differ diff --git a/gameSource/graphics/verLine.tga b/gameSource/graphics/verLine.tga new file mode 100644 index 0000000..44720d7 Binary files /dev/null and b/gameSource/graphics/verLine.tga differ diff --git a/gameSource/graphics/walls.tga b/gameSource/graphics/walls.tga new file mode 100644 index 0000000..8d83dce Binary files /dev/null and b/gameSource/graphics/walls.tga differ diff --git a/gameSource/graphics/warning.tga b/gameSource/graphics/warning.tga new file mode 100644 index 0000000..c2d267b Binary files /dev/null and b/gameSource/graphics/warning.tga differ diff --git a/gameSource/graphics/zoom.tga b/gameSource/graphics/zoom.tga new file mode 100644 index 0000000..297de9d Binary files /dev/null and b/gameSource/graphics/zoom.tga differ diff --git a/gameSource/grid.png b/gameSource/grid.png new file mode 100644 index 0000000..a51b257 Binary files /dev/null and b/gameSource/grid.png differ diff --git a/gameSource/hints.png b/gameSource/hints.png new file mode 100644 index 0000000..40f5f2f Binary files /dev/null and b/gameSource/hints.png differ diff --git a/gameSource/hold.png b/gameSource/hold.png new file mode 100644 index 0000000..e36abea Binary files /dev/null and b/gameSource/hold.png differ diff --git a/gameSource/horLine.png b/gameSource/horLine.png new file mode 100644 index 0000000..0e7ed68 Binary files /dev/null and b/gameSource/horLine.png differ diff --git a/gameSource/imageCache.cpp b/gameSource/imageCache.cpp new file mode 100644 index 0000000..c39e6ae --- /dev/null +++ b/gameSource/imageCache.cpp @@ -0,0 +1,192 @@ +#include "imageCache.h" +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/log/AppLog.h" +#include "minorGems/io/file/File.h" +#include "minorGems/io/file/FileInputStream.h" +#include "minorGems/io/file/FileOutputStream.h" + + + +typedef struct imageCacheRecord { + uniqueID id; + char useTrans; + Image *image; + } imageCacheRecord; + + +static SimpleVector cacheRecords; + +static const char *cacheDirName = "imageCache"; + + + +void initImageCache() { + File cacheDir( NULL, cacheDirName ); + + if( !cacheDir.exists() ) { + cacheDir.makeDirectory(); + } + + if( ! cacheDir.exists() || ! cacheDir.isDirectory() ) { + AppLog::error( "Could not create an image cache directory" ); + } + } + + + +void freeImageCache() { + int numRecords = cacheRecords.size(); + for( int i=0; iimage; + } + cacheRecords.deleteAll(); + } + + + +static File *getCacheFile( uniqueID inID, char inUseTrans ) { + char *idString = getHumanReadableString( inID ); + + int transFlag = 0; + if( inUseTrans ) { + transFlag = 1; + } + + char *name = autoSprintf( "%s_%d", idString, inUseTrans ); + + delete [] idString; + + char *pathSteps[1]; + + pathSteps[0] = (char *)"imageCache"; + + File *cacheFile = new File( new Path( pathSteps, 1, false ), + name ); + + delete [] name; + + return cacheFile; + } + + + +void addCachedImage( uniqueID inID, char inUseTrans, Image *inImage ) { + imageCacheRecord r = { inID, inUseTrans, inImage }; + cacheRecords.push_back( r ); + + // limit size + if( cacheRecords.size() > 63 ) { + // drop oldest + delete cacheRecords.getElement(0)->image; + cacheRecords.deleteElement( 0 ); + //printf( "Deleting excess record from image cache.\n" ); + } + + File *cacheFile = getCacheFile( inID, inUseTrans ); + + if( ! cacheFile->exists() ) { + + FileOutputStream out( cacheFile ); + + inImage->serialize( &out ); + + char *error = out.getLastError(); + if( error != NULL ) { + AppLog::error( "Failed to write image cache file" ); + AppLog::error( error ); + delete [] error; + } + } + delete cacheFile; + } + + + + +// returns NULL if image not found in cache +Image *getCachedImage( uniqueID inID, char inUseTrans ) { + int numRecords = cacheRecords.size(); + for( int i=numRecords-1; i>=0; i-- ) { + imageCacheRecord r = *( cacheRecords.getElement(i) ); + + if( equal( r.id, inID ) && inUseTrans == r.useTrans ) { + // found + + // move to head + cacheRecords.deleteElement( i ); + cacheRecords.push_back( r ); + + return r.image; + } + } + + + // not found in memory? + + // try disk + File *cacheFile = getCacheFile( inID, inUseTrans ); + + if( cacheFile->exists() ) { + + FileInputStream in( cacheFile ); + + Image *image = new Image( 1, 1, 4, false ); + + image->deserialize( &in ); + + char *error = in.getLastError(); + if( error != NULL ) { + AppLog::error( "Failed to read image cache file" ); + AppLog::error( error ); + delete [] error; + } + else { + // add it + addCachedImage( inID, inUseTrans, image ); + + delete cacheFile; + + return image; + } + } + + delete cacheFile; + + + return NULL; + } + + + +void clearCachedImages( uniqueID inID ) { + int numRecords = cacheRecords.size(); + for( int i=numRecords-1; i>=0; i-- ) { + imageCacheRecord r = *( cacheRecords.getElement(i) ); + + if( equal( r.id, inID ) ) { + // found + + + // delete and keep looking + delete cacheRecords.getElement(i)->image; + cacheRecords.deleteElement( i ); + } + } + + // delete both + File *cacheFile = getCacheFile( inID, 0 ); + + if( cacheFile->exists() ) { + cacheFile->remove(); + } + delete cacheFile; + + cacheFile = getCacheFile( inID, 1 ); + + if( cacheFile->exists() ) { + cacheFile->remove(); + } + delete cacheFile; + } + + diff --git a/gameSource/imageCache.h b/gameSource/imageCache.h new file mode 100644 index 0000000..1637452 --- /dev/null +++ b/gameSource/imageCache.h @@ -0,0 +1,33 @@ + +// cache for generated images that are frequently re-generated, but that +// don't change as long as inID remains the same +// caching saves generation time (especially for slow-to-generate ones like +// Rooms and StateObjects). + + +#include "uniqueID.h" + +#include "minorGems/graphics/Image.h" + + + + +void initImageCache(); + + +// free before program termination +void freeImageCache(); + + +// inImage destroyed by the cache eventually +void addCachedImage( uniqueID inID, char inUseTrans, Image *inImage ); + + + +// returns NULL if image not found in cache... caller does not destroy result +Image *getCachedImage( uniqueID inID, char inUseTrans ); + + + +// clears any images associated with this id (trans or non-trans) +void clearCachedImages( uniqueID inID ); diff --git a/gameSource/labels.cpp b/gameSource/labels.cpp new file mode 100644 index 0000000..85d872b --- /dev/null +++ b/gameSource/labels.cpp @@ -0,0 +1,26 @@ +#include "labels.h" + +extern TextGL *largeText; + + +#include "minorGems/util/TranslationManager.h" + + +EightPixelLabel::EightPixelLabel( double inAnchorX, double inAnchorY, + const char *inTranslationKey, + double inScaleFactor ) + : LabelGL( inAnchorX, inAnchorY, 0, 0, "", largeText ) { + + + char *string = (char *)TranslationManager::translate( inTranslationKey ); + + + double height = 8 * inScaleFactor; + + double width = height * strlen( string ); + + setText( string ); + setPosition( inAnchorX, inAnchorY, width, height ); + } + + diff --git a/gameSource/labels.h b/gameSource/labels.h new file mode 100644 index 0000000..08a3201 --- /dev/null +++ b/gameSource/labels.h @@ -0,0 +1,21 @@ +#ifndef LABELS_INCLUDED +#define LABELS_INCLUDED + + +#include "minorGems/graphics/openGL/gui/LabelGL.h" + + +// label that sets its own size based on the default font +// automatically translates its string +class EightPixelLabel : public LabelGL { + + public: + + EightPixelLabel( double inAnchorX, double inAnchorY, + const char *inTranslationKey, + double inScaleFactor = 1.0 ); + + }; + + +#endif diff --git a/gameSource/language.txt b/gameSource/language.txt new file mode 100644 index 0000000..3d38949 --- /dev/null +++ b/gameSource/language.txt @@ -0,0 +1 @@ +English \ No newline at end of file diff --git a/gameSource/languages/English.txt b/gameSource/languages/English.txt new file mode 100644 index 0000000..44b943f --- /dev/null +++ b/gameSource/languages/English.txt @@ -0,0 +1,344 @@ +writeFailed "Error: game folder is read-only" + +enterDemoCode "Enter a demo code:" +checkingCode "Checking demo permissions with server..." + + +title "Sleep Is Death (Geisterfahrer)" +volume "Adjust music volume" +volumeDone "Press any key when done" + +escToQuit "Press ESC to quit" + +control_local "Host a Local or Manual Network game as Controller." +control_remote "Host an Automatic (UPNP) Remote Internet game as Controller." +joinAsPlayer "Join a hosted game as Player." + +waitingForPlayer "Waiting for Player to connect to the following address:" +doNotWaitTip "(press G to start playing without a connection)" +portTip "Port:" + + +upnpTrying "Trying to open a port on your router with UPNP..." +upnpCloseTrying "Closing the router port with UPNP..." +upnpSuccess "Port opened successfully." +upnpFailure "Failed to open a router port with UPNP." + + + +enterAddress "Enter the Controller's address:" + +joiningController "Joining game..." +joiningFailed "Failed to connect." + +loading "Loading..." + +quitQuestion "Really quit? Press Y..." + + + +search "Search:" +stack "Stack:" + +tile "Tile:" +tileSetName "Tile set name:" + +room "Room:" +roomName "Room name:" + + +sprite "Sprite:" +spriteName "Sprite name:" + + +object "Object:" +objectName "Object name:" + + +music "Phrase:" +musicName "Phrase name:" + +timbre "Timbre:" +timbreName "Timbre name:" + +scale "Scale:" +scaleName "Scale name:" + +song "Song:" +songName "Song name:" + + +scene "Scene:" +sceneName "Scene name:" + +palette "Palette:" +paletteName "Palette name:" + + + + +tip_undo "Undo (Ctrl-Z)" +tip_redo "Redo" + +tip_next "Next page" +tip_prev "Previous page" +tip_searchMode "Switch to search mode" +tip_stackMode "Switch to stack mode" + +tip_flipH "Flip horizontally" +tip_flipV "Flip vertically" +tip_rotateCCW "Rotate counter-clockwise" +tip_rotateCW "Rotate clockwise" + +tip_clear "Clear" + +tip_colorize "Colorize all pixels" + + +tip_pen "Draw single squares" +tip_horLine "Fill horizontal lines" +tip_verLine "Fill vertical lines" +tip_fill "Flood fill" +tip_stamp "Stamp in copies of selected pixels" + +tip_pickColor "Copy color from a square (Shift-click)" +tip_pickTile "Copy tile from a square (Shift-click)" +tip_pickPhrase "Copy phrase from a square (Shift-click)" + +tip_selection "Toggle selection used by Stamp tool" + + +tip_walls "Toggle wall editing" +tip_hints "Toggle object hints from scene" + + +tip_delete_tile "Delete unused tile" +tip_delete_room "Delete unused room" +tip_delete_sprite "Delete unused sprite" +tip_delete_object "Delete unused object" +tip_delete_scene "Delete scene" +tip_delete_music "Delete unused phrase" +tip_delete_timbre "Delete unused timbre" +tip_delete_scale "Delete unused scale" +tip_delete_song "Delete song" +tip_delete_palette "Delete palette" +tip_confirm " -- Confirm" + +tip_trans "Toggle background tiles" +tip_erase "Toggle erase mode" + +tip_objMove "Select or move object" +tip_objSpeak "Adjust speech for selected object" + +tip_delete_speech "Clear object speech" + +tip_toggle_bubble "Switch object's speech to a bubble" +tip_toggle_box "Switch object's speech to a box" + + +tip_grid "Toggle grid and object anchors" + +tip_freezePlayer "Toggle player position frozen" + +tip_lockSelected "Toggle lock on selected object" +tip_lockGlobal "Toggle locks enabled" + +tip_holdObject "Toggle object held across scene changes" + + + +tip_flip_speech "Flip speech bubble left-to-right" + +tip_objSet "Replace selected object in scene" +tip_objAdd "Add object to scene (Ctrl-A)" +tip_objEdit "Edit selected object in scene (Ctrl-E)" +tip_objRemove "Remove selected object from scene (Ctrl-X)" + +tip_send "Commit and send state" + +tip_practice "Practice against timer" +tip_practiceStop "Stop practicing" + + +tip_room_fade "Fade room" + +tip_object_fade "Fade selected object" + + +tip_object_anchor "Object" +tip_player_anchor "Player object" +tip_anchor_selected "(selected)" + + + +tip_flipBook "Toggle image flip-book recording" + + +tip_obj_trans "Toggle background tiles" +tip_obj_grid "Toggle grid and object anchor" + +tip_next_layer "Next sprite layer" +tip_prev_layer "Previous sprite layer" + +tip_top_layer "Jump to top sprite layer" +tip_bottom_layer "Jump to bottom sprite layer" + +tip_layer_up "Move sprite layer up" +tip_layer_down "Move sprite layer down" + + +tip_new_layer "Add layer using selected sprite (Ctrl-A)" +tip_delete_layer "Delete sprite layer (Ctrl-X)" +tip_replace_layer "Replace layer with selected sprite" +tip_edit_layer "Edit this layer's sprite (Ctrl-E)" + +tip_zoom "Zoom in" +tip_unzoom "Zoom out" + +tip_rotateLayer "Rotate layer clockwise" +tip_rotateWholeObject "Rotate whole object" + +tip_flipLayer "Flip layer horizontally" +tip_flipWholeObject "Flip whole object horizontally" + +tip_fadeLayer "Fade layer" +tip_fadeWholeObject "Fade whole object" + +tip_glowLayer "Turn glow mode on for layer" +tip_glowWholeObject "Turn glow mode on for whole object" + +tip_noGlowLayer "Turn glow mode off for layer" +tip_noGlowWholeObject "Turn glow mode off for whole object" + + +tip_draggingSprite "Dragging sprite into object" +tip_draggingObjectObject "Dragging object into object" +tip_draggingObjectScene "Dragging object into scene" + +tip_objectFull "Object is full" +tip_sceneFull "Scene is full" + + + +tip_colorWell "Color well" + +tip_colorCurrent "Current color" + +tip_addColor "Add color to selected color well" + +tip_valueSlider "Value" + +tip_saturationSlider "Saturation" + + + +tip_edit_color "Edit color" +tip_edit_tile "Edit tile" +tip_edit_room "Edit room" +tip_edit_sprite "Edit sprite" +tip_edit_object "Edit object" +tip_edit_phrase "Edit phrase" +tip_edit_music "Edit music" +tip_edit_timbre "Edit timbre" +tip_edit_scale "Edit scale" +tip_edit_palette "Edit palette" + + +tip_closeEdit_color "Close color editor" +tip_closeEdit_tile "Close tile editor (Ctrl-W)" +tip_closeEdit_room "Close room editor (Ctrl-W)" +tip_closeEdit_sprite "Close sprite editor (Ctrl-W)" +tip_closeEdit_object "Close object editor (Ctrl-W)" +tip_closeEdit_music "Close phrase editor (Ctrl-W)" +tip_closeEdit_song "Close music editor (Ctrl-W)" +tip_closeEdit_timbre "Close timbre editor (Ctrl-W)" +tip_closeEdit_scale "Close scale editor (Ctrl-W)" +tip_closeEdit_palette "Close palette editor (Ctrl-W)" + + +tip_addTile "Save tile and add it to database" +tip_addRoom "Save room and add it to database" +tip_addSprite "Save sprite and add it to database" +tip_addObject "Save object and add it to database" +tip_addMusic "Save phrase and add it to database" +tip_addSong "Save song and add it to database" +tip_addTimbre "Save timbre and add it to database" +tip_addScale "Save scale and add it to database" +tip_addScene "Save scene and add it to database" +tip_addPalette "Save palette and add it to database" + + + + + +tip_playerMove "Move" +tip_playerSpeak "Speak" +tip_playerAct "Act" + +tip_playerSend "Commit and send move" + + +tip_delete_playerSpeech "Clear speech" +tip_delete_playerAction "Clear action" + + + +tip_noConnection "No connection" +tip_waiting "Waiting..." + +tip_timer "Time left until move sent" + +tip_noConnection_address "No connection, address" + + + +tip_addSceneToPack "Add scene to resource pack" +tip_sceneAlreadyInPack "Scene already in resource pack" +tip_savePack "Save the accumulated resource pack" + +tip_addSongToPack "Add song to resource pack" +tip_songAlreadyInPack "Song already in resource pack" + +tip_addPaletteToPack "Add palette to resource pack" +tip_paletteAlreadyInPack "Palette already in resource pack" + +tip_octave "Octave" + +tip_attack "Note attack time" +tip_hold "Note hold time" +tip_release "Note release time" + + +tip_trackPhrase "Track phrase" +tip_trackPhraseEmpty "Empty phrase slot" +tip_trackTimbre "Track timbre" +tip_trackLoudness "Track loudness" +tip_trackStereo "Track stereo pan" + + +tip_normalSpeed "Medium" +tip_fastSpeed "Fast" +tip_slowSpeed "Slow" + + +warning_sending_soon "Your move will be sent soon" +warning_move_received "Player move received" + + +instruction_playerMove "Move with mouse" +instruction_playerSpeak "Type to speak" +instruction_playerAct "Type a verb, direct with mouse" + + + + +err_webRequest "ERROR: Web request failed." +err_codeFailed "This demo code was denied by the server." + + +err_failedConnect "ERROR: Failed to connect." +err_failedAccept "ERROR: Failed to accept a connection." +err_failedSend "ERROR: Failed to send a network message." +err_failedReceive "ERROR: Failed to receive a network message." + +err_receiveCorrupted "ERROR: Received a corrupted network message." \ No newline at end of file diff --git a/gameSource/last.png b/gameSource/last.png new file mode 100644 index 0000000..183e032 Binary files /dev/null and b/gameSource/last.png differ diff --git a/gameSource/layerDown.png b/gameSource/layerDown.png new file mode 100644 index 0000000..514ba47 Binary files /dev/null and b/gameSource/layerDown.png differ diff --git a/gameSource/layerGrab.png b/gameSource/layerGrab.png new file mode 100644 index 0000000..a68685c Binary files /dev/null and b/gameSource/layerGrab.png differ diff --git a/gameSource/layerReplace.png b/gameSource/layerReplace.png new file mode 100644 index 0000000..988edc2 Binary files /dev/null and b/gameSource/layerReplace.png differ diff --git a/gameSource/layerUp.png b/gameSource/layerUp.png new file mode 100644 index 0000000..d4db507 Binary files /dev/null and b/gameSource/layerUp.png differ diff --git a/gameSource/left.png b/gameSource/left.png new file mode 100644 index 0000000..a50aa85 Binary files /dev/null and b/gameSource/left.png differ diff --git a/gameSource/lock.png b/gameSource/lock.png new file mode 100644 index 0000000..32eb4f6 Binary files /dev/null and b/gameSource/lock.png differ diff --git a/gameSource/lockSelected.png b/gameSource/lockSelected.png new file mode 100644 index 0000000..8b221b1 Binary files /dev/null and b/gameSource/lockSelected.png differ diff --git a/gameSource/mac/SDLMain.h b/gameSource/mac/SDLMain.h new file mode 100644 index 0000000..4683df5 --- /dev/null +++ b/gameSource/mac/SDLMain.h @@ -0,0 +1,11 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import + +@interface SDLMain : NSObject +@end diff --git a/gameSource/mac/SDLMain.m b/gameSource/mac/SDLMain.m new file mode 100644 index 0000000..122fcc8 --- /dev/null +++ b/gameSource/mac/SDLMain.m @@ -0,0 +1,384 @@ +/* SDLMain.m - main entry point for our Cocoa-ized SDL app + Initial Version: Darrell Walisser + Non-NIB-Code & other changes: Max Horn + + Feel free to customize this file to suit your needs +*/ + +#import +#import "SDLMain.h" +#import /* for MAXPATHLEN */ +#import + +/* For some reaon, Apple removed setAppleMenu from the headers in 10.4, + but the method still is there and works. To avoid warnings, we declare + it ourselves here. */ +@interface NSApplication(SDL_Missing_Methods) +- (void)setAppleMenu:(NSMenu *)menu; +@end + +/* Use this flag to determine whether we use SDLMain.nib or not */ +#define SDL_USE_NIB_FILE 0 + +/* Use this flag to determine whether we use CPS (docking) or not */ +#define SDL_USE_CPS 1 +#ifdef SDL_USE_CPS +/* Portions of CPS.h */ +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +#endif /* SDL_USE_CPS */ + +static int gArgc; +static char **gArgv; +static BOOL gFinderLaunch; +static BOOL gCalledAppMainline = FALSE; + +static NSString *getApplicationName(void) +{ + NSDictionary *dict; + NSString *appName = 0; + + /* Determine the application name */ + dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); + if (dict) + appName = [dict objectForKey: @"CFBundleName"]; + + if (![appName length]) + appName = [[NSProcessInfo processInfo] processName]; + + return appName; +} + +#if SDL_USE_NIB_FILE +/* A helper category for NSString */ +@interface NSString (ReplaceSubString) +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; +@end +#endif + +@interface SDLApplication : NSApplication +@end + +@implementation SDLApplication +/* Invoked from the Quit menu item */ +- (void)terminate:(id)sender +{ + /* Post a SDL_QUIT event */ + SDL_Event event; + event.type = SDL_QUIT; + SDL_PushEvent(&event); +} +@end + +/* The main class of the application, the application's delegate */ +@implementation SDLMain + +/* Set the working directory to the .app's parent directory */ +- (void) setupWorkingDirectory:(BOOL)shouldChdir +{ + if (shouldChdir) + { + char parentdir[MAXPATHLEN]; + CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); + if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) { + assert ( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ + } + CFRelease(url); + CFRelease(url2); + } + +} + +#if SDL_USE_NIB_FILE + +/* Fix menu to contain the real app name instead of "SDL App" */ +- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName +{ + NSRange aRange; + NSEnumerator *enumerator; + NSMenuItem *menuItem; + + aRange = [[aMenu title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; + + enumerator = [[aMenu itemArray] objectEnumerator]; + while ((menuItem = [enumerator nextObject])) + { + aRange = [[menuItem title] rangeOfString:@"SDL App"]; + if (aRange.length != 0) + [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; + if ([menuItem hasSubmenu]) + [self fixMenu:[menuItem submenu] withAppName:appName]; + } + [ aMenu sizeToFit ]; +} + +#else + +static void setApplicationMenu(void) +{ + /* warning: this code is very odd */ + NSMenu *appleMenu; + NSMenuItem *menuItem; + NSString *title; + NSString *appName; + + appName = getApplicationName(); + appleMenu = [[NSMenu alloc] initWithTitle:@""]; + + /* Add menu items */ + title = [@"About " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Hide " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; + + menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; + [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; + + [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; + + [appleMenu addItem:[NSMenuItem separatorItem]]; + + title = [@"Quit " stringByAppendingString:appName]; + [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; + + + /* Put menu into the menubar */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; + [menuItem setSubmenu:appleMenu]; + [[NSApp mainMenu] addItem:menuItem]; + + /* Tell the application object that this is now the application menu */ + [NSApp setAppleMenu:appleMenu]; + + /* Finally give up our references to the objects */ + [appleMenu release]; + [menuItem release]; +} + +/* Create a window menu */ +static void setupWindowMenu(void) +{ + NSMenu *windowMenu; + NSMenuItem *windowMenuItem; + NSMenuItem *menuItem; + + windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + + /* "Minimize" item */ + menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; + [windowMenu addItem:menuItem]; + [menuItem release]; + + /* Put menu into the menubar */ + windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; + [windowMenuItem setSubmenu:windowMenu]; + [[NSApp mainMenu] addItem:windowMenuItem]; + + /* Tell the application object that this is now the window menu */ + [NSApp setWindowsMenu:windowMenu]; + + /* Finally give up our references to the objects */ + [windowMenu release]; + [windowMenuItem release]; +} + +/* Replacement for NSApplicationMain */ +static void CustomApplicationMain (int argc, char **argv) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + SDLMain *sdlMain; + + /* Ensure the application object is initialised */ + [SDLApplication sharedApplication]; + +#ifdef SDL_USE_CPS + { + CPSProcessSerNum PSN; + /* Tell the dock about us */ + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [SDLApplication sharedApplication]; + } +#endif /* SDL_USE_CPS */ + + /* Set up the menubar */ + [NSApp setMainMenu:[[NSMenu alloc] init]]; + setApplicationMenu(); + setupWindowMenu(); + + /* Create SDLMain and make it the app delegate */ + sdlMain = [[SDLMain alloc] init]; + [NSApp setDelegate:sdlMain]; + + /* Start the main event loop */ + [NSApp run]; + + [sdlMain release]; + [pool release]; +} + +#endif + + +/* + * Catch document open requests...this lets us notice files when the app + * was launched by double-clicking a document, or when a document was + * dragged/dropped on the app's icon. You need to have a + * CFBundleDocumentsType section in your Info.plist to get this message, + * apparently. + * + * Files are added to gArgv, so to the app, they'll look like command line + * arguments. Previously, apps launched from the finder had nothing but + * an argv[0]. + * + * This message may be received multiple times to open several docs on launch. + * + * This message is ignored once the app's mainline has been called. + */ +- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename +{ + const char *temparg; + size_t arglen; + char *arg; + char **newargv; + + if (!gFinderLaunch) /* MacOS is passing command line args. */ + return FALSE; + + if (gCalledAppMainline) /* app has started, ignore this document. */ + return FALSE; + + temparg = [filename UTF8String]; + arglen = SDL_strlen(temparg) + 1; + arg = (char *) SDL_malloc(arglen); + if (arg == NULL) + return FALSE; + + newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); + if (newargv == NULL) + { + SDL_free(arg); + return FALSE; + } + gArgv = newargv; + + SDL_strlcpy(arg, temparg, arglen); + gArgv[gArgc++] = arg; + gArgv[gArgc] = NULL; + return TRUE; +} + + +/* Called when the internal event loop has just started running */ +- (void) applicationDidFinishLaunching: (NSNotification *) note +{ + int status; + + /* Set the working directory to the .app's parent directory */ + [self setupWorkingDirectory:gFinderLaunch]; + +#if SDL_USE_NIB_FILE + /* Set the main menu to contain the real app name instead of "SDL App" */ + [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; +#endif + + /* Hand off to main application code */ + gCalledAppMainline = TRUE; + status = SDL_main (gArgc, gArgv); + + /* We're done, thank you for playing */ + exit(status); +} +@end + + +@implementation NSString (ReplaceSubString) + +- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString +{ + unsigned int bufferSize; + unsigned int selfLen = [self length]; + unsigned int aStringLen = [aString length]; + unichar *buffer; + NSRange localRange; + NSString *result; + + bufferSize = selfLen + aStringLen - aRange.length; + buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar)); + + /* Get first part into buffer */ + localRange.location = 0; + localRange.length = aRange.location; + [self getCharacters:buffer range:localRange]; + + /* Get middle part into buffer */ + localRange.location = 0; + localRange.length = aStringLen; + [aString getCharacters:(buffer+aRange.location) range:localRange]; + + /* Get last part into buffer */ + localRange.location = aRange.location + aRange.length; + localRange.length = selfLen - localRange.location; + [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; + + /* Build output string */ + result = [NSString stringWithCharacters:buffer length:bufferSize]; + + NSDeallocateMemoryPages(buffer, bufferSize); + + return result; +} + +@end + + + +#ifdef main +# undef main +#endif + + +/* Main entry point to executable - should *not* be SDL_main! */ +int main (int argc, char **argv) +{ + /* Copy the arguments into a global variable */ + /* This is passed if we are launched by double-clicking */ + if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { + gArgv = (char **) SDL_malloc(sizeof (char *) * 2); + gArgv[0] = argv[0]; + gArgv[1] = NULL; + gArgc = 1; + gFinderLaunch = YES; + } else { + int i; + gArgc = argc; + gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); + for (i = 0; i <= argc; i++) + gArgv[i] = argv[i]; + gFinderLaunch = NO; + } + +#if SDL_USE_NIB_FILE + [SDLApplication poseAsClass:[NSApplication class]]; + NSApplicationMain (argc, argv); +#else + CustomApplicationMain (argc, argv); +#endif + return 0; +} + diff --git a/gameSource/makeFileList b/gameSource/makeFileList new file mode 100644 index 0000000..77443b0 --- /dev/null +++ b/gameSource/makeFileList @@ -0,0 +1,221 @@ +LAYER_SOURCE = \ +game.cpp \ +common.cpp \ +uniqueID.cpp \ +resourceManager.cpp \ +resourceDatabase.cpp \ +StringTree.cpp \ +buttons.cpp \ +Connection.cpp \ +GameHalf.cpp \ +PlayerGame.cpp \ +ControllerGame.cpp \ +Tile.cpp \ +Room.cpp \ +Editor.cpp \ +ColorEditor.cpp \ +HueValuePicker.cpp \ +ColorWells.cpp \ +BorderPanel.cpp \ +TileEditor.cpp \ +Sprite.cpp \ +DrawToolSet.cpp \ +labels.cpp \ +ResourcePicker.cpp \ +TilePicker.cpp \ +RoomPicker.cpp \ +RoomEditor.cpp \ +SpriteResource.cpp \ +SpritePicker.cpp \ +SpriteEditor.cpp \ +GameState.cpp \ +GameStateEditor.cpp \ +GameStateDisplay.cpp \ +StateToolSet.cpp \ +TransformToolSet.cpp \ +ToolTipManager.cpp \ +ToolTipDisplay.cpp \ +StateObject.cpp \ +StateObjectPicker.cpp \ +StateObjectDisplay.cpp \ +StateObjectEditor.cpp \ +PlayerMoveEditor.cpp \ +MoveToolSet.cpp \ +TimerDisplay.cpp \ +FixedTipDisplay.cpp \ +ToolTipComponentGL.cpp \ +musicPlayer.cpp \ +Envelope.cpp \ +Timbre.cpp \ +NoteGridDisplay.cpp \ +MusicEditor.cpp \ +imageCache.cpp \ +ToolTipSliderGL.cpp \ +DemoCodeChecker.cpp \ +SelectionManager.cpp \ +WarningDisplay.cpp \ +DragAndDropManager.cpp \ +Music.cpp \ +MusicPicker.cpp \ +Scene.cpp \ +ScenePicker.cpp \ +packSaver.cpp \ +PressActionGUIPanelGL.cpp \ +fixOldResources.cpp \ +TimbreResource.cpp \ +TimbrePicker.cpp \ +TimbreEditor.cpp \ +Song.cpp \ +SongPicker.cpp \ +SongEditor.cpp \ +Scale.cpp \ +ScalePicker.cpp \ +ScaleEditor.cpp \ +resourceImporter.cpp \ +usageDatabase.cpp \ +Palette.cpp \ +PalettePicker.cpp \ +PaletteEditor.cpp \ +GridOverlay.cpp \ +speechHints.cpp \ + + + + + +GAME_GRAPHICS = \ +graphics/font_8_16.tga \ +graphics/left.tga \ +graphics/right.tga \ +graphics/edit.tga \ +graphics/close.tga \ +graphics/pickColor.tga \ +graphics/fill.tga \ +graphics/pen.tga \ +graphics/horLine.tga \ +graphics/verLine.tga \ +graphics/stamp.tga \ +graphics/selection.tga \ +graphics/undo.tga \ +graphics/redo.tga \ +graphics/delete.tga \ +graphics/confirm.tga \ +graphics/walls.tga \ +graphics/hints.tga \ +graphics/trans.tga \ +graphics/transSmall.tga \ +graphics/anchor.tga \ +graphics/playerAnchor.tga \ +graphics/move.tga \ +graphics/speak.tga \ +graphics/send.tga \ +graphics/sendConfirm.tga \ +graphics/smallAdd.tga \ +graphics/speechBubble.tga \ +graphics/speechBox.tga \ +graphics/bubbleTop.tga \ +graphics/bubbleMiddle.tga \ +graphics/bubbleMiddleExtra.tga \ +graphics/bubbleMiddleExtraThin.tga \ +graphics/bubbleBottom.tga \ +graphics/bubbleBottomFlip.tga \ +graphics/bubbleBottomNoTail.tga \ +graphics/bubbleMiddleTail.tga \ +graphics/bubbleMiddleTailFlip.tga \ +graphics/speechBoxTop.tga \ +graphics/speechBoxBottom.tga \ +graphics/actionBoxStart.tga \ +graphics/actionBoxMiddle.tga \ +graphics/actionBoxMiddleExtra.tga \ +graphics/actionBoxEnd.tga \ +graphics/actionBoxEndFlip.tga \ +graphics/actionBoxStartTall.tga \ +graphics/actionBoxMiddleTall.tga \ +graphics/actionBoxMiddleExtraTall.tga \ +graphics/actionBoxEndTall.tga \ +graphics/actionBoxEndFlipTall.tga \ +graphics/grid.tga \ +graphics/setObject.tga \ +graphics/addObject.tga \ +graphics/editObject.tga \ +graphics/removeObject.tga \ +graphics/replaceSprite.tga \ +graphics/editSprite.tga \ +graphics/removeSprite.tga \ +graphics/editRoom.tga \ +graphics/flipH.tga \ +graphics/flipV.tga \ +graphics/rotateCCW.tga \ +graphics/rotateCW.tga \ +graphics/zoom.tga \ +graphics/unzoom.tga \ +graphics/layerGrab.tga \ +graphics/layerReplace.tga \ +graphics/act.tga \ +graphics/clear.tga \ +graphics/search.tga \ +graphics/stack.tga \ +graphics/freeze.tga \ +graphics/note.tga \ +graphics/noteSpace.tga \ +graphics/music.tga \ +graphics/flipBook.tga \ +graphics/first.tga \ +graphics/last.tga \ +graphics/warning.tga \ +graphics/erase.tga \ +graphics/lock.tga \ +graphics/lockSelected.tga \ +graphics/colorSpot.tga \ +graphics/pack.tga \ +graphics/packSave.tga \ +graphics/packAlreadyIn.tga \ +graphics/practice.tga \ +graphics/practiceStop.tga \ +graphics/noDrop.tga \ +graphics/canDrop.tga \ +graphics/emptyPhrase.tga \ +graphics/timeMark.tga \ +graphics/octave.tga \ +graphics/scaleSpace.tga \ +graphics/editScale.tga \ +graphics/fast.tga \ +graphics/slow.tga \ +graphics/glow.tga \ +graphics/noGlow.tga \ +graphics/hold.tga \ +graphics/colorize.tga \ +graphics/layerUp.tga \ +graphics/layerDown.tga \ + + +NEEDED_MINOR_GEMS_OBJECTS = \ + ${SCREEN_GL_SDL_O} \ + ${SINGLE_TEXTURE_GL_O} \ + ${TYPE_IO_O} \ + ${STRING_UTILS_O} \ + ${STRING_BUFFER_OUTPUT_STREAM_O} \ + ${PATH_O} \ + ${TIME_O} \ + ${THREAD_O} \ + ${MUTEX_LOCK_O} \ + ${PNG_IMAGE_CONVERTER_O} \ + ${TRANSLATION_MANAGER_O} \ + ${SOCKET_O} \ + ${HOST_ADDRESS_O} \ + ${SOCKET_CLIENT_O} \ + ${SOCKET_SERVER_O} \ + ${NETWORK_FUNCTION_LOCKS_O} \ + ${LOOKUP_THREAD_O} \ + ${WEB_REQUEST_O} \ + ${SETTINGS_MANAGER_O} \ + ${FINISHED_SIGNAL_THREAD_O} \ + ${SHA1_O} \ + ${ENCODING_UTILS_O} \ + ${DIRECTORY_O} \ + ${PORT_MAPPING_O} \ + ${LOG_O} \ + ${APP_LOG_O} \ + ${FILE_LOG_O} \ + ${PRINT_LOG_O} \ + ${PRINT_UTILS_O} \ diff --git a/gameSource/move.png b/gameSource/move.png new file mode 100644 index 0000000..3d8a30c Binary files /dev/null and b/gameSource/move.png differ diff --git a/gameSource/music.png b/gameSource/music.png new file mode 100644 index 0000000..4332ef6 Binary files /dev/null and b/gameSource/music.png differ diff --git a/gameSource/musicPlayer.cpp b/gameSource/musicPlayer.cpp new file mode 100644 index 0000000..7fb4574 --- /dev/null +++ b/gameSource/musicPlayer.cpp @@ -0,0 +1,1125 @@ + +#include "musicPlayer.h" + +#include "common.h" +#include "Timbre.h" +#include "Envelope.h" +#include "Song.h" + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/log/AppLog.h" +#include "minorGems/system/Time.h" + + + +#include +#include + +#include +#include + + +// smoothly fade in particular tracks based on track fade level +// low value plays only first track... high value plays all tracks +extern double musicTrackFadeLevel; + + +// whether note is currently on and playing or not +// toggle these to control music as it plays + +char noteToggles[PARTS][S][N][N]; + +int partLengths[PARTS]; +// phrase position within each part +int partPositions[PARTS]; + + +double partLoudness[PARTS]; +double partStereo[PARTS]; + + +// last column of notes that sounded within each current tone matrix +int lastNoteColumnPlayed; + + + + + +int sampleRate = 22050; +//int sampleRate = 11025; + + + +// 16x16 tone matrix used in each phrase of each part +int w = N; +int h = N; + +// total number of samples played so far +int streamSamples = 0; + +// offset into grid at start +// for testing +int gridStartOffset = 0; + + + +// overal loudness of music +double musicLoudness = 1.0; + + + + + + +// one grid step in seconds +double gridStepDuration = 0.5; +int gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); + + +void setSpeed( int inSpeed ) { + SDL_LockAudio(); + + //double oldDuration = gridStepDuration; + + switch( inSpeed ) { + case 0: + gridStepDuration = 1; + break; + case 1: + gridStepDuration = 0.5; + break; + case 2: + gridStepDuration = 0.25; + break; + } + + //double speedMultiple = gridStepDuration / oldDuration; + + gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); + + // jump in stream to maintain our current grid location + // otherwise, we're in danger of playing the same note twice simultaneously + + SDL_UnlockAudio(); + } + + +//double entireGridDuraton; + + +// c +double v13KeyFrequency = 261.63; + +// actually, can't support high notes in the key of c w/out round-off errors +// because this is a wavetable implementation, and when the tables get short, +// the errors get huge +// THIS, however, evenly divides our sample rate (22050) +// which means that we get perfect, whole-number wave tables at octaves +double v14KeyFrequency = 172.265625; + +double keyFrequency = v13KeyFrequency; + +// sets v13 frequency by default, UNTIL a timbre is changed (because only +// a v14 game would do that). +// Thus, v13 music, when we're player, still sounds right. + + + + +int numTimbres = PARTS; + +Timbre *musicTimbres[ PARTS ]; + +int numEnvelopes = PARTS; + +Envelope *musicEnvelopes[ PARTS ]; + + +// waiting for destruction +SimpleVector oldTimbres; +SimpleVector oldEnvelopes; + + + + +class Note { + public: + // index into musicTimbres array + int mTimbreNumber; + + // pointer to actual Timbre used when note was last sounded + // (so it doesn't change out from under note, in middle of note) + Timbre *mTimbrePointer; + + + // index into musicEnvelopes array + int mEnvelopeNumber; + + // pointer to actual Envelope, for same reason as above + Envelope *mEnvelopePointer; + + + int mScaleNoteNumber; + + // additional loudness adjustment + // places note in stereo space + double mLoudnessLeft; + double mLoudnessRight; + + + // start time, in seconds from start of note grid + //double mStartTime; + + // duration in seconds + //double mDuration; + + // used when note is currently playing to track progress in note + // negative if we should wait before starting to play the note + int mCurrentSampleNumber; + + // duration in samples, set each time the note is playe + // incase speed changes + int mNumSamples; + + // set once + int mNumGridSteps; + + + // carry the grid step duration with us as we play, in case + // it is changed before we're done playing + int mOurGridStepDurationInSamples; + + + Note *copy() { + Note *note = new Note(); + + note->mTimbreNumber = mTimbreNumber; + note->mTimbrePointer = mTimbrePointer; + + note->mEnvelopeNumber = mEnvelopeNumber; + + note->mEnvelopePointer = mEnvelopePointer; + + note->mScaleNoteNumber = mScaleNoteNumber; + note->mLoudnessLeft = mLoudnessLeft; + note->mLoudnessRight = mLoudnessRight; + note->mCurrentSampleNumber = mCurrentSampleNumber; + + note->mNumSamples = mNumSamples; + note->mNumGridSteps = mNumGridSteps; + note->mOurGridStepDurationInSamples = + mOurGridStepDurationInSamples; + + return note; + } + + }; + + +// all possible notes in a 16x16 phrase grid + +// indexed as noteGrid[part][y][x] +Note *noteGrid[PARTS][N][N]; + + + + + +SimpleVector currentlyPlayingNotes; + + + +// need to synch these with audio thread + +void setMusicLoudness( double inLoudness ) { + SDL_LockAudio(); + + musicLoudness = inLoudness; + + SDL_UnlockAudio(); + } + + + +double getMusicLoudness() { + return musicLoudness; + } + + + +void restartMusic() { + SDL_LockAudio(); + + // return to beginning (and forget samples we've played so far) + streamSamples = 0; + + // drop all currently-playing notes + currentlyPlayingNotes.deleteAll(); + + SDL_UnlockAudio(); + } + + + + +// called by SDL to get more samples +void audioCallback( void *inUserData, Uint8 *inStream, int inLengthToFill ) { + + // 2 bytes for each channel of stereo sample + int numSamples = inLengthToFill / 4; + + + Sint16 *samplesL = new Sint16[ numSamples ]; + Sint16 *samplesR = new Sint16[ numSamples ]; + + // first, zero-out the buffer to prepare it for our sum of note samples + // each sample is 2 bytes + memset( samplesL, 0, 2 * numSamples ); + memset( samplesR, 0, 2 * numSamples ); + + + int i; + + + // hop through all grid steps that *start* in this stream buffer + // add notes that start during this stream buffer + + // how far into stream buffer before we hit our first grid step? + int startOfFirstGridStep = streamSamples % gridStepDurationInSamples; + + if( startOfFirstGridStep != 0 ) { + startOfFirstGridStep = + gridStepDurationInSamples - startOfFirstGridStep; + } + + + // hop from start of grid step to start of next grid step + // ignore samples in between, since notes don't start there, + // and all we're doing right now is finding notes that start + for( i=startOfFirstGridStep; + i 0 ) { + + // step in part + partPositions[si] = (x / w) % ( partLengths[si] ); + + + // step in tone matrix for that part-step + int matrixX = x % w; + + lastNoteColumnPlayed = matrixX; + + for( int y=0; ycopy(); + + currentlyPlayingNotes.push_back( note ); + + // save pointer to active envelope and timbre + // when this note began + note->mTimbrePointer = musicTimbres[ + note->mTimbreNumber ]; + note->mEnvelopePointer = musicEnvelopes[ + note->mEnvelopeNumber ]; + + note->mTimbrePointer->mActiveNoteCount ++; + note->mEnvelopePointer->mActiveNoteCount ++; + + // tweak loudness based on part loudness and stereo + note->mLoudnessRight *= partLoudness[ si ]; + note->mLoudnessLeft *= partLoudness[ si ]; + + // constant power rule + double p = M_PI * partStereo[ si ] / 2; + + note->mLoudnessRight *= sin( p ); + note->mLoudnessLeft *= cos( p ); + + + + // start it + + + // base these on our + // envelope (instead of currently set duration in + // player, which may have been updated before + // the envelopes were properly updated due to thread + // interleaving issues) + int envGridStepDuration = + musicEnvelopes[ note->mEnvelopeNumber ]-> + mGridStepDurationInSamples; + + // compute length + note->mNumSamples = + note->mNumGridSteps * envGridStepDuration; + + note->mOurGridStepDurationInSamples = + envGridStepDuration; + + + // set a delay for its start based on our position + // in this callback buffer + note->mCurrentSampleNumber = -i; + } + } + } + else { + partPositions[si] = 0; + } + + } + } + + + + streamSamples += numSamples; + + + // loop over all current notes and add their samples to buffer + + for( int n=0; nmScaleNoteNumber; + //Timbre *timbre = musicTimbres[ note->mTimbreNumber ]; + Timbre *timbre = note->mTimbrePointer; + int tableLength = timbre->mWaveTableLengths[ waveTableNumber ]; + + Sint16 *waveTable = timbre->mWaveTable[ waveTableNumber ]; + + //Envelope *env = musicEnvelopes[ note->mEnvelopeNumber ]; + Envelope *env = note->mEnvelopePointer; + double *envLevels = + env->getEnvelope( + // index envelope by number of grid steps in note + note->mNumSamples / + note->mOurGridStepDurationInSamples ); + + + double noteLoudnessL = note->mLoudnessLeft; + double noteLoudnessR = note->mLoudnessRight; + + // do this outside inner loop + noteLoudnessL *= musicLoudness; + noteLoudnessR *= musicLoudness; + + // factor in externally-set track fade level + + // level from 0..(numTimbres) + double trackFadeInLevel = musicTrackFadeLevel * (numTimbres); + + // level for this track based on trackFadeInLevel + double thisTrackLevel; + + if( trackFadeInLevel >= note->mTimbreNumber + 1 ) { + // full volume + thisTrackLevel = 1.0; + } + else if( trackFadeInLevel > note->mTimbreNumber ) { + // linear fade in for this track + thisTrackLevel = trackFadeInLevel - (int)trackFadeInLevel; + } + else { + // track silent + thisTrackLevel = 0; + } + + noteLoudnessL *= thisTrackLevel; + noteLoudnessR *= thisTrackLevel; + + + + int noteStartInBuffer = 0; + int noteEndInBuffer = numSamples; + + if( note->mCurrentSampleNumber < 0 ) { + // delay before note starts in this sample buffer + noteStartInBuffer = - note->mCurrentSampleNumber; + + // we've taken account of the delay + note->mCurrentSampleNumber = 0; + } + + char endNote = false; + + int numSamplesLeftInNote = + note->mNumSamples - note->mCurrentSampleNumber; + + if( noteStartInBuffer + numSamplesLeftInNote < noteEndInBuffer ) { + // note ends before end of buffer + noteEndInBuffer = noteStartInBuffer + numSamplesLeftInNote; + endNote = true; + } + + + int waveTablePos = note->mCurrentSampleNumber % tableLength; + + int currentSampleNumber = note->mCurrentSampleNumber; + + for( i=noteStartInBuffer; i != noteEndInBuffer; i++ ) { + double envelope = envLevels[ currentSampleNumber ]; + + double monoSample = envelope * + waveTable[ waveTablePos ]; + + + samplesL[i] += (Sint16)( noteLoudnessL * monoSample ); + samplesR[i] += (Sint16)( noteLoudnessR * monoSample ); + + currentSampleNumber ++; + + waveTablePos ++; + + // avoid using mod operator (%) in inner loop + // found with profiler + if( waveTablePos == tableLength ) { + // back to start of table + waveTablePos = 0; + } + + } + + note->mCurrentSampleNumber += ( noteEndInBuffer - noteStartInBuffer ); + + if( endNote ) { + // note ended in this buffer + currentlyPlayingNotes.deleteElement( n ); + n--; + + // note not using these anymore + note->mTimbrePointer->mActiveNoteCount --; + note->mEnvelopePointer->mActiveNoteCount --; + + if( note->mTimbrePointer->mActiveNoteCount == 0 && + musicTimbres[ note->mTimbreNumber ] != note->mTimbrePointer ) { + // this timbre is no longer used + + oldTimbres.deleteElementEqualTo( note->mTimbrePointer ); + delete note->mTimbrePointer; + + } + + if( note->mEnvelopePointer->mActiveNoteCount == 0 && + musicEnvelopes[ note->mEnvelopeNumber ] != + note->mEnvelopePointer ) { + // this envelope is no longer used + + oldEnvelopes.deleteElementEqualTo( note->mEnvelopePointer ); + delete note->mEnvelopePointer; + } + + // this was a copy + delete note; + } + + } + + + // now copy samples into Uint8 buffer + int streamPosition = 0; + for( i=0; i != numSamples; i++ ) { + Sint16 intSampleL = samplesL[i]; + Sint16 intSampleR = samplesR[i]; + + inStream[ streamPosition ] = (Uint8)( intSampleL & 0xFF ); + inStream[ streamPosition + 1 ] = (Uint8)( ( intSampleL >> 8 ) & 0xFF ); + + inStream[ streamPosition + 2 ] = (Uint8)( intSampleR & 0xFF ); + inStream[ streamPosition + 3 ] = (Uint8)( ( intSampleR >> 8 ) & 0xFF ); + + streamPosition += 4; + } + + delete [] samplesL; + delete [] samplesR; + + } + + + +// limit on n, based on Nyquist, when summing sine components +//int nLimit = (int)( sampleRate * M_PI ); +// actually, this is way too many: it takes forever to compute +// use a lower limit instead +// This produces fine results (almost perfect square wave) +int nLimit = 40; + + + +// square wave with period of 2pi +double squareWave( double inT ) { + double sum = 0; + + for( int n=1; nmActiveNoteCount > 0 ) { + // save old one, because some currently-playing notes are using it! + oldTimbres.push_back( musicTimbres[inTimbreNumber] ); + } + else { + delete musicTimbres[inTimbreNumber]; + } + + + + musicTimbres[inTimbreNumber] = newTimbre; + + SDL_UnlockAudio(); + } + + + + + +void setScale( char inToneOn[12] ) { + setTimbreScale( inToneOn ); + } + + + + +void setEnvelope( int inTimbreNumber, + double inAttack, double inHold, + double inRelease ) { + + if( inAttack + inHold + inRelease > 1.0 ) { + AppLog::error( + "Attack + Hold + Release in specified envelope too long" ); + if( inAttack > 1 ) { + inAttack = 1; + } + inHold = 1 - inAttack; + inRelease = 0; + } + + int maxNoteLength = 3; + + Envelope *newEnvelope = new Envelope( inAttack, inHold, + inRelease, + maxNoteLength, + maxNoteLength, + gridStepDurationInSamples ); + // replace it + SDL_LockAudio(); + + if( musicEnvelopes[inTimbreNumber]->mActiveNoteCount > 0 ) { + // save old one, because some currently-playing notes are using it! + oldEnvelopes.push_back( musicEnvelopes[inTimbreNumber] ); + } + else { + delete musicEnvelopes[inTimbreNumber]; + } + + + + musicEnvelopes[inTimbreNumber] = newEnvelope; + + SDL_UnlockAudio(); + } + + + +void setDefaultMusicSounds() { + // back to v13 default + keyFrequency = v13KeyFrequency; + + + gridStepDuration = 0.5; + gridStepDurationInSamples = (int)( gridStepDuration * sampleRate ); + + + setDefaultScale(); + + + SDL_LockAudio(); + + for( int i=0; imActiveNoteCount > 0 ) { + + // save old one, because some currently-playing notes + // are using it! + oldTimbres.push_back( musicTimbres[i] ); + } + else { + delete musicTimbres[i]; + } + } + if( musicEnvelopes[i] != NULL ) { + if( musicEnvelopes[i]->mActiveNoteCount > 0 ) { + + // save old one, because some currently-playing notes + // are using it! + oldEnvelopes.push_back( musicEnvelopes[i] ); + } + else { + delete musicEnvelopes[i]; + } + } + } + + + int heightPerTimbre = h; + + + // possible for all notes in a column to be on at user's request + // and notes are 3 long at max (decays), so consider overlap + double maxNoteLoudnessInAColumn = h * 3; + + + + // divide loudness amoung timbres to avoid clipping + double loudnessPerTimbre = 1.0 / maxNoteLoudnessInAColumn; + + // further adjust loudness per channel here as we construct + // each timbre. + + //double t = Time::getCurrentTime(); + + + // load defaults into first 3 banks. + // thus, music sent from v13 and earlier (if we'r Player) will + // play correctly + musicTimbres[0] = new Timbre( sampleRate, 1.0 * loudnessPerTimbre, + keyFrequency / 4, + heightPerTimbre, sawWave ); + + musicTimbres[1] = new Timbre( sampleRate, 0.75 * loudnessPerTimbre, + keyFrequency / 2, + heightPerTimbre, harmonicSaw ); + + // last timbre has one extra note at top (top row of grid) + musicTimbres[2] = new Timbre( sampleRate, 0.65 * loudnessPerTimbre, + keyFrequency, + heightPerTimbre + 1, harmonicSine ); + + + for( int i=3; ilogPrintf( + Log::INFO_LEVEL, + "Max note length in song = %d\n", maxNoteLength ); + + + // load defaults into first 3 banks. + // thus, music sent from v13 and earlier (if we'r Player) will + // play correctly + + musicEnvelopes[0] = new Envelope( 0.02, 0.98, 0, 0, + maxNoteLength, + maxNoteLength, + gridStepDurationInSamples ); + musicEnvelopes[1] = new Envelope( 0.15, 0.85, 0.0, 0.0, + maxNoteLength, + maxNoteLength, + gridStepDurationInSamples ); + + musicEnvelopes[2] = new Envelope( 0.01, 0.99, 0.0, 0.0, + maxNoteLength, + maxNoteLength, + gridStepDurationInSamples ); + + + for( int i=3; ilogPrintf( + Log::INFO_LEVEL, + "Height in grid per timbre = %d\n", heightPerTimbre ); + + + // nullify so they are not added to old list by setDefaultMusicSounds + for( int i=0; imScaleNoteNumber = noteNumber; + + noteGrid[si][y][x]->mTimbreNumber = si; + + noteGrid[si][y][x]->mTimbrePointer = musicTimbres[ si ]; + + + // same as timbre number + noteGrid[si][y][x]->mEnvelopeNumber = + noteGrid[si][y][x]->mTimbreNumber; + + noteGrid[si][y][x]->mEnvelopePointer = musicEnvelopes[ si ]; + + + // left loudness fixed + noteGrid[si][y][x]->mLoudnessLeft = + leftLoudness[ noteGrid[si][y][x]->mTimbreNumber ]; + + // right loudness fixed + noteGrid[si][y][x]->mLoudnessRight = + rightLoudness[ noteGrid[si][y][x]->mTimbreNumber ]; + + + //noteGrid[si][y][x]->mStartTime = gridStepDuration * x; + + // three grid steps long, for overlap + //noteGrid[si][y][x]->mDuration = + // gridStepDuration * 3; + noteGrid[si][y][x]->mNumGridSteps = 3; + + // set this when the note is played + //noteGrid[si][y][x]->mNumSamples = + // gridStepDurationInSamples * 3; + } + } + } + + + for( int i=0; ilogPrintf( + Log::ERROR_LEVEL, + "Unable to open audio: %s\n", SDL_GetError() ); + } + + // set pause to 0 to start audio + SDL_PauseAudio(0); + + + } + + + +void stopMusic() { + SDL_CloseAudio(); + + + for( int i=0; i + + + +typedef struct packRecord { + const char *type; + uniqueID id; + char *wordString; + unsigned char *data; + int length; + } packRecord; + +SimpleVector runningPack; + + +void initPackSaver() { + } + + +void freePackSaver() { + for( int i=0; iwordString; + delete [] r->data; + } + runningPack.deleteAll(); + } + + + +char alreadyInPack( uniqueID inID ) { + // already present? + for( int i=0; iid, inID ) ) { + return true; + } + } + return false; + } + + + +void addToPack( const char *inResourceType, + uniqueID inID, + char *inWordString, + unsigned char *inData, int inLength ) { + + if( alreadyInPack( inID ) ) { + return; + } + + + // not found + // add + + unsigned char *data = new unsigned char[ inLength ]; + memcpy( data, inData, inLength ); + + packRecord r = { + inResourceType, + inID, + stringDuplicate( inWordString ), + data, + inLength }; + + runningPack.push_back( r ); + } + + + +// saves the pack and clears the running pack +void savePack() { + + + File packDir( NULL, "resourcePacks" ); + + if( !packDir.exists() ) { + packDir.makeDirectory(); + } + + if( packDir.exists() && packDir.isDirectory() ) { + + int numFiles; + File **childFiles = packDir.getChildFiles( &numFiles ); + + int largestNumber = 0; + + for( int i=0; igetFileName(); + + int number; + + int numRead = sscanf( name, "%d", &number ); + + if( numRead == 1 ) { + + if( number > largestNumber ) { + largestNumber = number; + } + } + delete [] name; + delete childFiles[i]; + } + + delete [] childFiles; + + + int newFileNumber = largestNumber + 1; + + + char *fileName = autoSprintf( "%d.pak", newFileNumber ); + + File *packFile = packDir.getChildFile( fileName ); + + delete [] fileName; + + char *fullFileName = packFile->getFullFileName(); + + + + + SimpleVector dataAccum; + + + // first, output number of chunks + unsigned char *countChars = getChars( runningPack.size() ); + + dataAccum.push_back( countChars, 4 ); + + for( int i=0; itype ), + strlen( r->type ) + 1 ); + dataAccum.push_back( r->id.bytes, U ); + dataAccum.push_back( (unsigned char *)( r->wordString ), + strlen( r->wordString ) + 1 ); + + unsigned char *lengthChars = getChars( r->length ); + + dataAccum.push_back( lengthChars, 4 ); + dataAccum.push_back( r->data, r->length ); + } + + unsigned char *data = dataAccum.getElementArray(); + int numBytes = dataAccum.size(); + + uLongf compressSize = compressBound( numBytes ); + + unsigned char *compressData = new unsigned char[ compressSize + 4 ]; + + // first 4 bytes are uncompressed size + unsigned char *sizeChars = getChars( numBytes ); + memcpy( compressData, sizeChars, 4 ); + + int result = compress2( &( compressData[4] ), &compressSize, + data, numBytes, + Z_BEST_COMPRESSION ); + + if( result == Z_OK ) { + char result = packFile->writeToFile( compressData, + compressSize + 4 ); + + if( !result ) { + AppLog::error( "Failed to write to pack file:" ); + AppLog::error( fullFileName ); + } + } + else { + AppLog::error( "Zlib compression failed\n" ); + } + delete [] data; + delete [] compressData; + + delete packFile; + delete [] fullFileName; + + } + + + + + for( int i=0; iwordString; + delete [] r->data; + } + runningPack.deleteAll(); + } + + + +void loadPacks() { + + File loadDir( NULL, "loadingBay" ); + + if( !loadDir.exists() ) { + loadDir.makeDirectory(); + } + + if( loadDir.exists() && loadDir.isDirectory() ) { + + int numFiles; + File **childFiles = loadDir.getChildFiles( &numFiles ); + + for( int i=0; igetFullFileName(); + + if( strstr( name, ".pak" ) != NULL ) { + // a pack file + + char *shortName = childFiles[i]->getFileName(); + char found; + char *doneFlagName = + replaceOnce( shortName, ".pak", + ".done", &found ); + delete [] shortName; + + File *doneFlagFile = loadDir.getChildFile( doneFlagName ); + delete [] doneFlagName; + + if( !doneFlagFile->exists() ) { + + + + int bytesLeft; + unsigned char *bytes = NULL; + + + int compressSize; + + unsigned char *compressData = + childFiles[i]->readFileContents( &compressSize ); + + if( compressData != NULL ) { + + // first 4 are uncompressed size + int numUsed; + + bytesLeft = + readInt( compressData, compressSize, &numUsed ); + + if( numUsed > 0 ) { + bytes = new unsigned char[ bytesLeft ]; + + + uLongf destLen = bytesLeft; + + int result = uncompress( + bytes, &destLen, + &( compressData[numUsed] ), + compressSize - numUsed ); + + if( result != Z_OK || + destLen != (unsigned int)bytesLeft ) { + + AppLog::error( "Zlib decompression failed" ); + + delete [] bytes; + bytes = NULL; + } + } + delete [] compressData; + } + + + + unsigned char *allBytes = bytes; + + + if( bytes != NULL ) { + + SimpleVector importedScenes; + SimpleVector importedObjects; + + + + int numUsed; + + int numChunks = readInt( bytes, bytesLeft, &numUsed ); + + if( numUsed > 0 ) { + bytesLeft -= numUsed; + bytes = &( bytes[numUsed] ); + + char chunkSuccess = true; + + for( int c=0; c 0 ) { + bytesLeft -= numUsed; + bytes = &( bytes[numUsed] ); + + + if( bytesLeft >= dataLength ) { + + saveResourceData( type, + id, + wordString, + bytes, + dataLength ); + + if( strcmp( type, "object" ) == 0 ) { + importedObjects.push_back( id ); + } + else if( + strcmp( type, "scene" ) == 0 ) { + + importedScenes.push_back( id ); + } + + + AppLog::trace( "Saved pack chunk" ); + + chunkSuccess = true; + + bytesLeft -= dataLength; + bytes = &( bytes[dataLength] ); + } + } + delete [] wordString; + } + delete [] type; + } + if( chunkSuccess ) { + AppLog::info( "Successfully loaded all chunks" + " from pack file." ); + } + else { + AppLog::error( "Failed to load a chunk" + " from pack file." ); + } + + // create a flag file + doneFlagFile->writeToFile( "done loading" ); + + + AppLog::info( "Running fix script on imported" + " resources from pack." ); + + uniqueID *sceneArray = + importedScenes.getElementArray(); + uniqueID *objectArray = + importedObjects.getElementArray(); + + fixOldResources( sceneArray, importedScenes.size(), + objectArray, importedObjects.size() ); + + delete [] sceneArray; + delete [] objectArray; + + AppLog::info( "Done running fix script." ); + } + delete [] allBytes; + } + else { + AppLog::error( "Failed to read pack file:" ); + AppLog::error( name ); + } + } // end check for done file existing + else { + AppLog::info( "Done flag file present for pak file:" ); + AppLog::info( name ); + } + + delete doneFlagFile; + } + + delete [] name; + delete childFiles[i]; + } + + delete [] childFiles; + } + + } + diff --git a/gameSource/packSaver.h b/gameSource/packSaver.h new file mode 100644 index 0000000..ab4c274 --- /dev/null +++ b/gameSource/packSaver.h @@ -0,0 +1,25 @@ + +#include "uniqueID.h" + + +void initPackSaver(); +void freePackSaver(); + + +// add to the running pack +// word string and data destroyed by caller (copied internally) +void addToPack( const char *inResourceType, + uniqueID inID, + char *inWordString, + unsigned char *inData, int inLength ); + +char alreadyInPack( uniqueID inID ); + + +// saves the pack and clears the running pack +void savePack(); + + +// loads all packs in the pack dir +void loadPacks(); + diff --git a/gameSource/pen.png b/gameSource/pen.png new file mode 100644 index 0000000..7e818ea Binary files /dev/null and b/gameSource/pen.png differ diff --git a/gameSource/pick.png b/gameSource/pick.png new file mode 100644 index 0000000..9ac9b85 Binary files /dev/null and b/gameSource/pick.png differ diff --git a/gameSource/pickColor.png b/gameSource/pickColor.png new file mode 100644 index 0000000..1aad425 Binary files /dev/null and b/gameSource/pickColor.png differ diff --git a/gameSource/playerAnchor.png b/gameSource/playerAnchor.png new file mode 100644 index 0000000..3e7af30 Binary files /dev/null and b/gameSource/playerAnchor.png differ diff --git a/gameSource/practice.png b/gameSource/practice.png new file mode 100644 index 0000000..5ba2de4 Binary files /dev/null and b/gameSource/practice.png differ diff --git a/gameSource/practiceStop.png b/gameSource/practiceStop.png new file mode 100644 index 0000000..9ab31ca Binary files /dev/null and b/gameSource/practiceStop.png differ diff --git a/gameSource/redo.png b/gameSource/redo.png new file mode 100644 index 0000000..0943843 Binary files /dev/null and b/gameSource/redo.png differ diff --git a/gameSource/removeObject.png b/gameSource/removeObject.png new file mode 100644 index 0000000..217f774 Binary files /dev/null and b/gameSource/removeObject.png differ diff --git a/gameSource/removeSprite.png b/gameSource/removeSprite.png new file mode 100644 index 0000000..6097e9e Binary files /dev/null and b/gameSource/removeSprite.png differ diff --git a/gameSource/replaceSprite.png b/gameSource/replaceSprite.png new file mode 100644 index 0000000..308db85 Binary files /dev/null and b/gameSource/replaceSprite.png differ diff --git a/gameSource/resourceDatabase.cpp b/gameSource/resourceDatabase.cpp new file mode 100644 index 0000000..717c943 --- /dev/null +++ b/gameSource/resourceDatabase.cpp @@ -0,0 +1,690 @@ +#include "resourceDatabase.h" +#include "StringTree.h" + + +// naive implementation for now with no intelligent index +// as performance issues grow, see this article: + +// http://en.wikipedia.org/wiki/Substring_index + +#include "minorGems/util/SimpleVector.h" +#include "minorGems/system/Time.h" + +#include "stdio.h" +#include "minorGems/io/file/File.h" + +#include "minorGems/util/log/AppLog.h" + + +static File *getFullDBFile() { + char *pathSteps[1]; + + pathSteps[0] = (char*)"resourceCache"; + + + File *dbFile = new File( new Path( pathSteps, 1, false ), + "stringDatabase.txt" ); + + return dbFile; + } + + + + +typedef struct resourceRecord { + const char *type; + char *wordString; + uniqueID id; + } resourceRecord; + + +void printResourceRecord( void *inR ) { + resourceRecord *r = (resourceRecord*)inR; + char *idString = getHumanReadableString( r->id ); + + printf( "Type %s, keyword %s, ID %s\n", + r->type, r->wordString, idString ); + delete [] idString; + } + + +static SimpleVector dataLines; + + +static SimpleVector records; + + +// resource types stored in each tree +// one tree +static SimpleVector recordTreeTypes; +static SimpleVector recordTrees; + + + + +// hash table for quick check for duplicates on addData call + +// number of bins +#define B 2000 + + + +SimpleVector hashBins[B]; + +int getHashKey( uniqueID inID ) { + unsigned int sum = 0; + for( int i=0; iid ) ].push_back( inRecord ); + } + +void hashRemove( resourceRecord *inRecord ) { + uniqueID id = inRecord->id; + SimpleVector *bin = &( hashBins[ getHashKey( id ) ] ); + + int numEntries = bin->size(); + for( int i=0; igetElement( i ) ); + if( equal( r->id, id ) ) { + bin->deleteElement( i ); + return; + } + } + } + +char hashExists( uniqueID inID ) { + SimpleVector *bin = &( hashBins[ getHashKey( inID ) ] ); + + int numEntries = bin->size(); + for( int i=0; igetElement( i ) ); + if( equal( r->id, inID ) ) { + return true; + } + } + return false; + } + + +resourceRecord *hashLookup( uniqueID inID ) { + SimpleVector *bin = &( hashBins[ getHashKey( inID ) ] ); + + int numEntries = bin->size(); + for( int i=0; igetElement( i ) ); + if( equal( r->id, inID ) ) { + return r; + } + } + return NULL; + } + + + + + +StringTree *getTreeForType( const char *inType ) { + int numTrees = recordTrees.size(); + for( int i=0; iexists() ) { + + char *fileContents = fullDBFile->readFileContents(); + + delete fullDBFile; + + + if( fileContents == NULL ) { + AppLog::error( + "Error: failed to read from stringDatabase.txt\n" ); + return NULL; + } + + int numLines; + char **lines = split( fileContents, "\n", &numLines ); + + delete [] fileContents; + + *outNumLines = numLines; + return lines; + } + else { + AppLog::error( "Error: stringDatabase.txt does not exist\n" ); + } + + delete fullDBFile; + return NULL; + } + + + + +void initDatabase() { + + + + double start = Time::getCurrentTime(); + + + writeNewEntriesToFile = false; + + int numLines; + char **lines = getDataFileLines( &numLines ); + + + if( lines != NULL ) { + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, "Splitting %d lines took %d ms\n", + numLines, (int)( 1000 * (Time::getCurrentTime() - start ) ) ); + + + for( int i=0; ilogPrintf( + Log::ERROR_LEVEL, + "Failed to read unique ID from line %d of string DB\n", + i ); + } + else { + + // first 12 chars is unique ID + + char *idString = new char[ U * 2 + 1 ]; + + memcpy( idString, line, U * 2 ); + + idString[ U * 2 ] = '\0'; + + uniqueID id; + char result = parseHumanReadableString( idString, &id ); + + if( !result ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to read unique ID from " + "line %d of string DB\n", i ); + } + else { + + // skip ID and space + char *substring = &( line[ U * 2 + 1 ] ); + + + char typeString[100]; + + int numRead = sscanf( substring, "%99s", typeString ); + + if( numRead == 1 ) { + char *skipPointer = &substring[ strlen( typeString ) + + 1 ]; + + // rest of contents is word string + addData( typeString, id, skipPointer ); + } + } + + + delete [] idString; + } + + } + delete [] lines; + } + + writeNewEntriesToFile = true; + + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Loading database cache from disk took %d ms\n", + (int)( 1000 * (Time::getCurrentTime() - start ) ) ); + } + + + +void freeDatabase() { + for( int i=0; itype; + delete [] r->wordString; + + delete r; + } + records.deleteAll(); + + for( int i=0; itype = stringDuplicate( inResourceType ); + + r->wordString = stringDuplicate( inWordString ); + r->id = inID; + + + records.push_back( r ); + hashInsert( r ); + + + + // New code: string trees + StringTree *t = getTreeForType( inResourceType ); + + // lower case to make searches case-insensitive + char *lowerCase = stringToLowerCase( r->wordString ); + t->insert( lowerCase, (void *)r ); + delete [] lowerCase; + + + if( writeNewEntriesToFile ) { + + File *fullDBFile = getFullDBFile(); + + char *fullFileName = fullDBFile->getFullFileName(); + + delete fullDBFile; + + + FILE *f = fopen( fullFileName, "a" ); + + delete [] fullFileName; + + char *idString = getHumanReadableString( inID ); + + char *line = autoSprintf( "%s %s %s", + idString, inResourceType, inWordString ); + + dataLines.push_back( line ); + + + fprintf( f, "\n%s", line ); + fclose( f ); + + delete [] idString; + + /* + printf( "Adding data took %d ms\n", + (int)( 1000 * (Time::getCurrentTime() - start ) ) ); + */ + } + } + + + +void removeData( const char *inResourceType, + uniqueID inID ) { + + //double start = Time::getCurrentTime(); + + char found = false; + + for( int i=0; iid ) && + strcmp( inResourceType, r->type ) == 0 ) { + + // New code: string trees + StringTree *t = getTreeForType( inResourceType ); + + + // lower case for case-insensitive + char *lowerCase = stringToLowerCase( r->wordString ); + t->remove( lowerCase, (void *)r ); + + delete [] lowerCase; + + records.deleteElement( i ); + hashRemove( r ); + + delete [] r->type; + delete [] r->wordString; + delete r; + + found = true; + } + } + + + + File *fullDBFile = getFullDBFile(); + + char *fullFileName = fullDBFile->getFullFileName(); + delete fullDBFile; + + + FILE *f = fopen( fullFileName, "w" ); + + delete [] fullFileName; + + + + char *idString = getHumanReadableString( inID ); + char doneSkipping = false; + + char someLinesWritten = false; + + for( int i=0; i *getUnionOfWordMatches( + SimpleVector *inWords, const char *inResourceType ) { + + StringTree *t = getTreeForType( (char*)inResourceType ); + + + SimpleVector< resourceRecord *> *unionMatches = + new SimpleVector< resourceRecord *>(); + + + for( int i=0; isize(); i++ ) { + int wordCount = + t->countMatches( *( inWords->getElement(i) ) ); + + resourceRecord **values = new resourceRecord *[wordCount]; + + // -1 means get all + t->getMatches( *( inWords->getElement(i) ), 0, wordCount, + (void **)values ); + + if( i == 0 ) { + // populate union with first set + for( int w=0; wpush_back( values[w] ); + } + } + else { + for( int u=0; usize(); u++ ) { + // is this member of union in next set? + resourceRecord *unionMember = + *( unionMatches->getElement( u ) ); + + char found = false; + + for( int w=0; wdeleteElement( u ); + u--; + } + } + } + delete [] values; + } + + return unionMatches; + } + + + + +int countSearchResults( const char *inResourceType, + const char *inSearchString ) { + + //double start = Time::getCurrentTime(); + + int count = 0; + + // New code: string trees + StringTree *t = getTreeForType( (char*)inResourceType ); + + // lower-case in tree + char *lowerSearchString = stringToLowerCase( inSearchString ); + + + + if( lowerSearchString[0] == '\0' ) { + // empty search, count all + count = t->countMatches( lowerSearchString ); + } + else { + // how many words? + SimpleVector *words = tokenizeString( lowerSearchString ); + + if( words->size() == 0 ) { + // ignore whitespace, count everything + count = t->countMatches( "" ); + } + else if( words->size() == 1 ) { + count = t->countMatches( *( words->getElement(0) ) ); + } + else { + // multi-word, take union of results + + SimpleVector< resourceRecord *> *unionMatches = + getUnionOfWordMatches( words, inResourceType ); + + // union now contains only element that match every word + count = unionMatches->size(); + + delete unionMatches; + } + + for( int i=0; isize(); i++ ) { + delete [] *( words->getElement( i ) ); + } + delete words; + } + + + delete [] lowerSearchString; + + /* + printf( "Counting %d results took %d ms\n", + count, (int)( 1000 * (Time::getCurrentTime() - start ) ) ); + */ + + return count; + } + + + +// caller allocates spaces for inNumToGet and passes pointer as outIDs +int getSearchResults( const char *inResourceType, + const char *inSearchString, + int inNumToSkip, + int inNumToGet, + uniqueID *outIDs ) { + + //double start = Time::getCurrentTime(); + + int numGotten = 0; + + // New code: string trees + StringTree *t = getTreeForType( (char*)inResourceType ); + + resourceRecord **values = new resourceRecord *[inNumToGet]; + + // lower-case in tree + char *lowerSearchString = stringToLowerCase( inSearchString ); + + + if( lowerSearchString == '\0' ) { + // empty search, consider everything + numGotten = t->getMatches( lowerSearchString, inNumToSkip, inNumToGet, + (void **)values ); + } + else { + // how many words? + SimpleVector *words = tokenizeString( lowerSearchString ); + + if( words->size() == 0 ) { + // ignore spaces, show everything + numGotten = t->getMatches( "", inNumToSkip, inNumToGet, + (void **)values ); + } + else if( words->size() == 1 ) { + // just this word (no whitespace + numGotten = t->getMatches( *( words->getElement(0) ), + inNumToSkip, inNumToGet, + (void **)values ); + } + else { + // take union of results from multiple words, THEN + // apply skip and limit + SimpleVector< resourceRecord *> *unionMatches = + getUnionOfWordMatches( words, inResourceType ); + + int count = unionMatches->size(); + + numGotten = 0; + + for( int i=0; igetElement( i + inNumToSkip ) ); + + numGotten ++; + } + } + + delete unionMatches; + } + + for( int i=0; isize(); i++ ) { + delete [] *( words->getElement( i ) ); + } + delete words; + } + + delete [] lowerSearchString; + + for( int i=0; iid; + } + delete [] values; + + /* + printf( "Gettingresults took %d ms\n", + (int)( 1000 * (Time::getCurrentTime() - start ) ) ); + */ + + return numGotten; + } + + + + +char *getResourceName( uniqueID inID ) { + resourceRecord *r = hashLookup( inID ); + + if( r != NULL ) { + return r->wordString; + } + else { + return NULL; + } + } + + diff --git a/gameSource/resourceDatabase.h b/gameSource/resourceDatabase.h new file mode 100644 index 0000000..f531987 --- /dev/null +++ b/gameSource/resourceDatabase.h @@ -0,0 +1,37 @@ +// searchable database associating keywords with resources + +#include "uniqueID.h" + + + +void initDatabase(); +void freeDatabase(); + + + +void addData( const char *inResourceType, + uniqueID inID, + const char *inWordString ); + + +void removeData( const char *inResourceType, + uniqueID inID ); + + + +int countSearchResults( const char *inResourceType, + const char *inSearchString ); + + +// caller allocates spaces for inNumToGet and passes pointer as outIDs +// returns the number returned +int getSearchResults( const char *inResourceType, + const char *inSearchString, + int inNumToSkip, + int inNumToGet, + uniqueID *outIDs ); + + +// fast access to resource name without loading resource +// result NOT destroyed by caller +char *getResourceName( uniqueID inID ); diff --git a/gameSource/resourceImporter.cpp b/gameSource/resourceImporter.cpp new file mode 100644 index 0000000..b8ae9ab --- /dev/null +++ b/gameSource/resourceImporter.cpp @@ -0,0 +1,247 @@ + +#include "resourceImporter.h" + + +#include "resourceManager.h" +#include "resourceDatabase.h" +#include "Connection.h" +#include "common.h" +#include "fixOldResources.h" + +#include + + + +#include "minorGems/io/file/File.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/log/AppLog.h" + + + +// save IDs of stuff we import so we can fix it later +SimpleVector importedScenes; +SimpleVector importedObjects; + + +static void processImportDBLines( char **inLines, int inNumLines, + File *inImportCacheDir ) { + + for( int i=0; ilogPrintf( + Log::ERROR_LEVEL, + "Failed to read unique ID from line %d of " + "imported string DB\n", i ); + } + else { + + // first 12 chars is unique ID + + char *idString = new char[ U * 2 + 1 ]; + + memcpy( idString, line, U * 2 ); + + idString[ U * 2 ] = '\0'; + + uniqueID id; + char result = parseHumanReadableString( idString, &id ); + + if( !result ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to read unique ID from " + "line %d of imported string DB\n", i ); + } + else { + + // skip ID and space + char *substring = &( line[ U * 2 + 1 ] ); + + + char typeString[100]; + + int numRead = sscanf( substring, "%99s", typeString ); + + if( numRead == 1 ) { + + // first, make sure resource does not already exist + if( !resourceExists( typeString, id ) ) { + + char *namePointer = &substring[ strlen( typeString ) + + 1 ]; + + File *resourceTypeDir = + inImportCacheDir->getChildFile( typeString ); + + if( resourceTypeDir->exists() && + resourceTypeDir->isDirectory() ) { + + + File *resourceFile = + resourceTypeDir->getChildFile( idString ); + + if( resourceFile->exists() ) { + + int dataLength; + unsigned char *fileData = + resourceFile->readFileContents( + &dataLength ); + + if( fileData != NULL ) { + + saveResourceData( + typeString, + id, + namePointer, + fileData, dataLength ); + + if( strcmp( typeString, "object" ) == 0 ) { + importedObjects.push_back( id ); + } + else if( + strcmp( typeString, "scene" ) == 0 ) { + + importedScenes.push_back( id ); + } + + delete [] fileData; + } + else { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to read file data for " + "line %d of imported string DB\n", i ); + } + } + else { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "File does not exist for " + "line %d of imported string DB\n", i ); + } + + + delete resourceFile; + } + else { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Resource type directory not exist for " + "line %d of imported string DB\n", i ); + } + + delete resourceTypeDir; + } + } + } + + + delete [] idString; + } + + } + } + + + + +void importResources() { + + File importDir( NULL, "importOldCache" ); + + if( !importDir.exists() ) { + AppLog::warning( "No importOldCache directory found, creating it" ); + importDir.makeDirectory(); + } + else if( !importDir.isDirectory() ) { + AppLog::error( "importOldCache is not a directory" ); + } + else { + File *cacheDir = importDir.getChildFile( "resourceCache" ); + + if( cacheDir->exists() && cacheDir->isDirectory() ) { + + AppLog::info( "Found a resource cache directory to import" ); + + + File *flagFile = + cacheDir->getChildFile( "alreadyImported.txt" ); + + if( flagFile->exists() ) { + AppLog::info( "Directory is flagged as already imported" ); + } + else { + AppLog::info( "Directory is NOT flagged as already imported" ); + + File *dbFile = cacheDir->getChildFile( "stringDatabase.txt" ); + + + if( dbFile->exists() ) { + + char *fileContents = dbFile->readFileContents(); + + + if( fileContents == NULL ) { + AppLog::error( + "Error: " + "failed to import from stringDatabase.txt" ); + } + else { + + int numLines; + char **lines = split( fileContents, "\n", &numLines ); + + delete [] fileContents; + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Importing %d resources from importOldCache", + numLines ); + + processImportDBLines( lines, numLines, cacheDir ); + + for( int i=0; iwriteToFile( "import complete" ); + } + } + delete dbFile; + } + delete flagFile; + + AppLog::info( "Done importing resource cache directory" ); + } + delete cacheDir; + } + } + diff --git a/gameSource/resourceImporter.h b/gameSource/resourceImporter.h new file mode 100644 index 0000000..786aad7 --- /dev/null +++ b/gameSource/resourceImporter.h @@ -0,0 +1,5 @@ + + + +// imports from the importOldCache dir +void importResources(); diff --git a/gameSource/resourceManager.cpp b/gameSource/resourceManager.cpp new file mode 100644 index 0000000..eaccbc5 --- /dev/null +++ b/gameSource/resourceManager.cpp @@ -0,0 +1,562 @@ +#include "resourceManager.h" +#include "resourceDatabase.h" +#include "Connection.h" +#include "common.h" + +#include + + + +#include "minorGems/io/file/File.h" +#include "minorGems/util/stringUtils.h" +#include "minorGems/util/SimpleVector.h" +#include "minorGems/util/log/AppLog.h" + +#include "minorGems/system/Thread.h" +#include "minorGems/system/Time.h" + + + + +extern Connection *connection; + + +char testResourceCacheWritePermissions() { + File cacheDir( NULL, "resourceCache" ); + + if( !cacheDir.exists() ) { + return true; + } + else { + File *writeFile = cacheDir.getChildFile( "testWrite.txt " ); + + char success = writeFile->writeToFile( "test" ); + + writeFile->remove(); + + delete writeFile; + + return success; + } + } + + + + +void initResourceManager() { + + File cacheDir( NULL, "resourceCache" ); + + if( !cacheDir.exists() ) { + AppLog::warning( "No resourceCache directory found, creating it" ); + cacheDir.makeDirectory(); + } + + if( cacheDir.exists() && cacheDir.isDirectory() ) { + // good + } + else { + AppLog::criticalError( + "ERROR: resourceCache not found, and could not create it" ); + } + } + + + +void freeResourceManager() { + } + + + + + + +static char useNetwork = true; + + +void setUseNetwork( char inUseNetwork ) { + useNetwork = inUseNetwork; + } + + +static File *getResourceDir( const char *inResourceType ) { + char *pathSteps[1]; + + pathSteps[0] = (char *)"resourceCache"; + + + File *resourceDir = new File( new Path( pathSteps, 1, false ), + (char*)inResourceType ); + + return resourceDir; + } + + + + +File *getResourceFile( const char *inResourceType, uniqueID inID ) { + + char *idString = getHumanReadableString( inID ); + + char *pathSteps[2]; + + pathSteps[0] = (char *)"resourceCache"; + pathSteps[1] = (char *)inResourceType; + + + File *resourceFile = new File( new Path( pathSteps, 2, false ), + idString ); + + delete [] idString; + + return resourceFile; + } + + + +static void addRequestMessage( SimpleVector *inMessageAccum, + const char *inResourceType, + uniqueID inID ) { + + int typeLength = strlen( inResourceType ); + inMessageAccum->push_back( (unsigned char)typeLength ); + inMessageAccum->push_back( (unsigned char *)inResourceType, typeLength ); + + inMessageAccum->push_back( inID.bytes, U ); + } + + + + +unsigned char *loadResourceData( const char *inResourceType, + uniqueID inID, + int *outLength, + char *outCameFromNetwork ) { + + File *resourceFile = getResourceFile( inResourceType, inID ); + + if( resourceFile->exists() ) { + + *outLength = resourceFile->getLength(); + + char *fullFileName = resourceFile->getFullFileName(); + + delete resourceFile; + + + FILE *f = fopen( fullFileName, "rb" ); + + delete [] fullFileName; + + unsigned char *data = new unsigned char[ *outLength ]; + + int numRead = fread( data, 1, *outLength, f ); + + fclose( f ); + + *outCameFromNetwork = false; + + if( numRead != *outLength ) { + delete [] data; + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to read from %s file\n", inResourceType ); + return NULL; + } + + + return data; + } + else if( useNetwork && + connection != NULL && connection->isConnected() ) { + + delete resourceFile; + + // try to fetch from network + + SimpleVector messageAccum; + + // single request in this bundle + messageAccum.push_back( 1 ); + + addRequestMessage( &messageAccum, inResourceType, inID ); + + unsigned char *message = messageAccum.getElementArray(); + + // resource requests on channel 1 + connection->sendMessage( message, messageAccum.size(), 1 ); + + delete [] message; + + + // now wait for response + + int length; + message = NULL; + + unsigned long sec, ms; + + Time::getCurrentTime( &sec, &ms ); + + char *idString = getHumanReadableString( inID ); + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "Waiting for %s resource %s from network (%lu:%lu)...\n", + inResourceType, idString, sec, ms ); + delete [] idString; + + while( message == NULL ) { + // keep stepping until network operations are finished + char workLeft = true; + while( workLeft ) { + workLeft = connection->step(); + } + + message = connection->receiveMessage( &length, 1 ); + + if( message == NULL ) { + // wait + AppLog::trace( "sleeping\n" ); + Thread::staticSleep( 50 ); + } + } + + Time::getCurrentTime( &sec, &ms ); + AppLog::getLog()->logPrintf( + Log::DETAIL_LEVEL, + "...got response (%lu:%lu)\n", sec, ms ); + + // Got message + + *outCameFromNetwork = true; + + // first 4 bytes represent length of chunk, skip them (only one chunk) + *outLength = length - 4; + + unsigned char *returnVal = new unsigned char[ length - 4 ]; + memcpy( returnVal, &( message[4] ), length - 4 ); + + delete [] message; + + return returnVal; + } + else { + delete resourceFile; + + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Failed to open %s file (does not exist)\n", inResourceType ); + + // fix: + // why were we trying to open it anyway? Remove it from + // database for future + removeData( (char*)inResourceType, inID ); + + // failure + return NULL; + } + + } + + + +unsigned char **loadResourceData( const char *inResourceType, + int inNumResources, + uniqueID *inIDs, + int *outLengths, + char *outCameFromNetwork ) { + + unsigned char **results = new unsigned char *[ inNumResources ]; + + int numToFetch = 0; + + // mark duplicates so that we can fill them in later w/out sending + // duplicate requests in our batch + int *duplicate = new int[ inNumResources ]; + + + for( int i=0; iexists() ) { + + // simply fetch from disk using single-function + results[i] = loadResourceData( inResourceType, + inIDs[i], + &( outLengths[i] ), + &( outCameFromNetwork[i] ) ); + duplicate[i] = -1; + } + else { + // flag and deal with in batch below + + // but only flag it if it is the *first* occurrence of this + // ID in the batch request (don't fetch same resource + // multiple times in same batch) + duplicate[i] = -1; + + int alreadyListed = false; + for( int j=0; j messageAccum; + + if( numToFetch > 255 ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error: too many resources fetched from network in one" + " batch: %d\n", numToFetch ); + // return null results + delete [] duplicate; + return results; + } + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Sending a batch request for %d [%s] resources\n", numToFetch, + inResourceType ); + + + // first, send number in batch + messageAccum.push_back( (unsigned char)numToFetch ); + + for( int i=0; isendMessage( message, messageAccum.size(), 1 ); + + delete [] message; + + + // now wait for response + int length; + message = NULL; + + unsigned long sec, ms; + + Time::getCurrentTime( &sec, &ms ); + + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "Waiting for batch of %s resources from network (%lu:%lu)...\n", + inResourceType, sec, ms ); + + while( message == NULL ) { + // keep stepping until network operations are finished + char workLeft = true; + while( workLeft ) { + workLeft = connection->step(); + } + + message = connection->receiveMessage( &length, 1 ); + + if( message == NULL ) { + // wait + AppLog::trace( "sleeping\n" ); + Thread::staticSleep( 50 ); + } + } + + Time::getCurrentTime( &sec, &ms ); + AppLog::getLog()->logPrintf( + Log::INFO_LEVEL, + "...got response (%lu:%lu)\n", sec, ms ); + + // Got message + + unsigned char *messageLeft = message; + int lengthLeft = length; + + // split into parts + for( int i=0; ilogPrintf( + Log::ERROR_LEVEL, + "Error:Failed to read resource chunk from message" ); + delete [] message; + delete [] duplicate; + return results; + } + + messageLeft = &( messageLeft[ numUsed ] ); + lengthLeft -= numUsed; + + + outLengths[i] = chunkLength; + + if( lengthLeft < chunkLength ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, + "Error:Failed to read resource chunk " + "from messag\n" ); + delete [] message; + delete [] duplicate; + return results; + } + + results[i] = new unsigned char[ chunkLength ]; + memcpy( results[i], messageLeft, chunkLength ); + + messageLeft = &( messageLeft[ chunkLength ] ); + lengthLeft -= chunkLength; + } + } + + + // fill in duplicates by simply copying fetched data + for( int i=0; iexists() ) { + AppLog::warning( "No appropriate resourceCache subdirectory " + "directory found, creating it" ); + resourceDir->makeDirectory(); + } + + if( resourceDir->exists() && resourceDir->isDirectory() ) { + // good + } + else { + AppLog::criticalError( + "ERROR: necessary resourceCache subdirectory not found, and " + "could not create it" ); + delete resourceDir; + return; + } + delete resourceDir; + + + File *resourceFile = getResourceFile( inResourceType, inID ); + + + if( resourceFile->exists() ) { + AppLog::info( "Resource collides with same ID already on disk\n" ); + + // but just because data file exists doesn't mean it has the same + // contents (rare chance of a hash collision... we want to overwrite + // the old data, not ignore the new data) + + AppLog::detail( " Removing old search data from database\n" ); + removeData( (char *)inResourceType, inID ); + + AppLog::detail( + " Adding replacement resource data into that ID slot\n" ); + } + + + char *fullFileName = resourceFile->getFullFileName(); + + FILE *f = fopen( fullFileName, "wb" ); + + delete [] fullFileName; + + + int numWritten = fwrite( inData, 1, inLength, f ); + + fclose( f ); + + if( numWritten != inLength ) { + AppLog::getLog()->logPrintf( + Log::ERROR_LEVEL, "Failed to write to %s file\n", inResourceType ); + } + + delete resourceFile; + + + // add to the search database + addData( (char*)inResourceType, inID, inWordString ); + } + + + +void deleteResource( const char *inResourceType, + uniqueID inID ) { + File *resourceFile = getResourceFile( inResourceType, inID ); + + resourceFile->remove(); + + delete resourceFile; + + removeData( (char*)inResourceType, inID ); + } + + + + +char resourceExists( const char *inResourceType, + uniqueID inID ) { + + File *resourceFile = getResourceFile( inResourceType, inID ); + + char exists = resourceFile->exists(); + + delete resourceFile; + + return exists; + } diff --git a/gameSource/resourceManager.h b/gameSource/resourceManager.h new file mode 100644 index 0000000..186b147 --- /dev/null +++ b/gameSource/resourceManager.h @@ -0,0 +1,63 @@ + +#include "uniqueID.h" + + +// tests if we can write to cache folder +// only returns false if it exists but is not writeable +// can be called before init +// (returns true if it does not exist) +char testResourceCacheWritePermissions(); + + +void initResourceManager(); +void freeResourceManager(); + + + + +// should failed resource loads be fetched from the network? +// if not, then NULL is returned on load failure. +// +// Defaults to true +void setUseNetwork( char inUseNetwork ); + + + + +// loads a resource from file or network, if not found in file +// caches resources fetched from network in file +// inResourceType is a string such as "tile", "sprite", etc. +// returns NULL on failure +// caller destroys result +// +// outCameFromNetwork flag passed out indicating that data was not found +// on disk and should be saved by caller (via saveResourceData) +unsigned char *loadResourceData( const char *inResourceType, + uniqueID inID, + int *outLength, + char *outCameFromNetwork ); + + +// loads data as a batch (more efficient if data coming across the network +unsigned char **loadResourceData( const char *inResourceType, + int inNumResources, + uniqueID *inIDs, + int *outLengths, + char *outCameFromNetwork ); + + + + +// saves to file +void saveResourceData( const char *inResourceType, + uniqueID inID, + char *inWordString, + unsigned char *inData, int inLength ); + + +void deleteResource( const char *inResourceType, + uniqueID inID ); + + +char resourceExists( const char *inResourceType, + uniqueID inID ); diff --git a/gameSource/resourcePack.tar.gz b/gameSource/resourcePack.tar.gz new file mode 100644 index 0000000..94606cb Binary files /dev/null and b/gameSource/resourcePack.tar.gz differ diff --git a/gameSource/resourcePack2.tar.gz b/gameSource/resourcePack2.tar.gz new file mode 100644 index 0000000..2cd9138 Binary files /dev/null and b/gameSource/resourcePack2.tar.gz differ diff --git a/gameSource/resourcePack3.tar.gz b/gameSource/resourcePack3.tar.gz new file mode 100644 index 0000000..82235c2 Binary files /dev/null and b/gameSource/resourcePack3.tar.gz differ diff --git a/gameSource/resourcePack4.tar.gz b/gameSource/resourcePack4.tar.gz new file mode 100644 index 0000000..3dae848 Binary files /dev/null and b/gameSource/resourcePack4.tar.gz differ diff --git a/gameSource/resourcePack5.tar.gz b/gameSource/resourcePack5.tar.gz new file mode 100644 index 0000000..04e4fc3 Binary files /dev/null and b/gameSource/resourcePack5.tar.gz differ diff --git a/gameSource/resourcePack6.tar.gz b/gameSource/resourcePack6.tar.gz new file mode 100644 index 0000000..b38f1cd Binary files /dev/null and b/gameSource/resourcePack6.tar.gz differ diff --git a/gameSource/resourcePack7.tar.gz b/gameSource/resourcePack7.tar.gz new file mode 100644 index 0000000..ec28868 Binary files /dev/null and b/gameSource/resourcePack7.tar.gz differ diff --git a/gameSource/resourceSet10.tar.gz b/gameSource/resourceSet10.tar.gz new file mode 100644 index 0000000..f9581fa Binary files /dev/null and b/gameSource/resourceSet10.tar.gz differ diff --git a/gameSource/resourceSet12.tar.gz b/gameSource/resourceSet12.tar.gz new file mode 100644 index 0000000..93336ca Binary files /dev/null and b/gameSource/resourceSet12.tar.gz differ diff --git a/gameSource/resourceSet14.tar.gz b/gameSource/resourceSet14.tar.gz new file mode 100644 index 0000000..92c3984 Binary files /dev/null and b/gameSource/resourceSet14.tar.gz differ diff --git a/gameSource/resourceSet15.tar.gz b/gameSource/resourceSet15.tar.gz new file mode 100644 index 0000000..badb7b1 Binary files /dev/null and b/gameSource/resourceSet15.tar.gz differ diff --git a/gameSource/right.png b/gameSource/right.png new file mode 100644 index 0000000..702148e Binary files /dev/null and b/gameSource/right.png differ diff --git a/gameSource/rotateCCW.png b/gameSource/rotateCCW.png new file mode 100644 index 0000000..8f7d5f4 Binary files /dev/null and b/gameSource/rotateCCW.png differ diff --git a/gameSource/rotateCW.png b/gameSource/rotateCW.png new file mode 100644 index 0000000..287496b Binary files /dev/null and b/gameSource/rotateCW.png differ diff --git a/gameSource/scaleSpace.png b/gameSource/scaleSpace.png new file mode 100644 index 0000000..49d89b6 Binary files /dev/null and b/gameSource/scaleSpace.png differ diff --git a/gameSource/search.png b/gameSource/search.png new file mode 100644 index 0000000..cc877d2 Binary files /dev/null and b/gameSource/search.png differ diff --git a/gameSource/selection.png b/gameSource/selection.png new file mode 100644 index 0000000..011da82 Binary files /dev/null and b/gameSource/selection.png differ diff --git a/gameSource/send.png b/gameSource/send.png new file mode 100644 index 0000000..2e40f75 Binary files /dev/null and b/gameSource/send.png differ diff --git a/gameSource/sendConfirm.png b/gameSource/sendConfirm.png new file mode 100644 index 0000000..f138fb3 Binary files /dev/null and b/gameSource/sendConfirm.png differ diff --git a/gameSource/setObject.png b/gameSource/setObject.png new file mode 100644 index 0000000..e32d59e Binary files /dev/null and b/gameSource/setObject.png differ diff --git a/gameSource/settings/autoHost.ini b/gameSource/settings/autoHost.ini new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/gameSource/settings/autoHost.ini @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/gameSource/settings/autoJoin.ini b/gameSource/settings/autoJoin.ini new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/gameSource/settings/autoJoin.ini @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/gameSource/settings/defaultServerAddress.ini b/gameSource/settings/defaultServerAddress.ini new file mode 100644 index 0000000..e69de29 diff --git a/gameSource/settings/flipBook.ini b/gameSource/settings/flipBook.ini new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/gameSource/settings/flipBook.ini @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/gameSource/settings/fullscreen.ini b/gameSource/settings/fullscreen.ini new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/gameSource/settings/fullscreen.ini @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/gameSource/settings/hardToQuitMode.ini b/gameSource/settings/hardToQuitMode.ini new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/gameSource/settings/hardToQuitMode.ini @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/gameSource/settings/port.ini b/gameSource/settings/port.ini new file mode 100644 index 0000000..f872751 --- /dev/null +++ b/gameSource/settings/port.ini @@ -0,0 +1 @@ +7778 \ No newline at end of file diff --git a/gameSource/settings/screenHeight.ini b/gameSource/settings/screenHeight.ini new file mode 100644 index 0000000..7ad8022 --- /dev/null +++ b/gameSource/settings/screenHeight.ini @@ -0,0 +1 @@ +480 \ No newline at end of file diff --git a/gameSource/settings/screenWidth.ini b/gameSource/settings/screenWidth.ini new file mode 100644 index 0000000..c328260 --- /dev/null +++ b/gameSource/settings/screenWidth.ini @@ -0,0 +1 @@ +640 \ No newline at end of file diff --git a/gameSource/settings/timeLimit.ini b/gameSource/settings/timeLimit.ini new file mode 100644 index 0000000..8580e7b --- /dev/null +++ b/gameSource/settings/timeLimit.ini @@ -0,0 +1 @@ +30 \ No newline at end of file diff --git a/gameSource/slow.png b/gameSource/slow.png new file mode 100644 index 0000000..3dab5ca Binary files /dev/null and b/gameSource/slow.png differ diff --git a/gameSource/smallAdd.png b/gameSource/smallAdd.png new file mode 100644 index 0000000..3fb1f7a Binary files /dev/null and b/gameSource/smallAdd.png differ diff --git a/gameSource/speak.png b/gameSource/speak.png new file mode 100644 index 0000000..cbef457 Binary files /dev/null and b/gameSource/speak.png differ diff --git a/gameSource/speechBox.png b/gameSource/speechBox.png new file mode 100644 index 0000000..e01c76a Binary files /dev/null and b/gameSource/speechBox.png differ diff --git a/gameSource/speechBoxBottom.png b/gameSource/speechBoxBottom.png new file mode 100644 index 0000000..1bfb157 Binary files /dev/null and b/gameSource/speechBoxBottom.png differ diff --git a/gameSource/speechBoxTop.png b/gameSource/speechBoxTop.png new file mode 100644 index 0000000..0ec7478 Binary files /dev/null and b/gameSource/speechBoxTop.png differ diff --git a/gameSource/speechBubble.png b/gameSource/speechBubble.png new file mode 100644 index 0000000..24efad9 Binary files /dev/null and b/gameSource/speechBubble.png differ diff --git a/gameSource/speechHints.cpp b/gameSource/speechHints.cpp new file mode 100644 index 0000000..46665e2 --- /dev/null +++ b/gameSource/speechHints.cpp @@ -0,0 +1,136 @@ +#include "speechHints.h" + +#include "minorGems/io/file/File.h" +#include "minorGems/util/log/AppLog.h" + + +static File *hintDir = NULL; + + +void initSpeechHints() { + File cacheDir( NULL, "resourceCache" ); + + if( !cacheDir.exists() ) { + AppLog::criticalError( + "No resourceCache directory found for speech hints" ); + } + else { + hintDir = cacheDir.getChildFile( "speechHints" ); + + + if( !hintDir->exists() ) { + AppLog::warning( "No speechHints directory found, creating it" ); + hintDir->makeDirectory(); + } + + if( hintDir->exists() && hintDir->isDirectory() ) { + // good + } + else { + AppLog::criticalError( + "ERROR: speechHints not found, and could not create it" ); + + delete hintDir; + hintDir = NULL; + } + } + } + + + +void freeSpeechHints() { + if( hintDir != NULL ) { + delete hintDir; + } + } + + + + +void setSpeechHint( uniqueID inObjectID, + intPair inSpeechOffset, char inSpeechflip ) { + + if( hintDir != NULL ) { + char *fileName = getHumanReadableString( inObjectID ); + + File *hintFile = hintDir->getChildFile( fileName ); + + SimpleVector fileAccum; + fileAccum.push_back( getChars( inSpeechOffset ), 8 ); + if( inSpeechflip ) { + fileAccum.push_back( 1 ); + } + else { + fileAccum.push_back( 0 ); + } + + unsigned char *fileData = fileAccum.getElementArray(); + hintFile->writeToFile( fileData, fileAccum.size() ); + delete [] fileData; + + + delete [] fileName; + delete hintFile; + } + + } + + +static intPair defaultHint = { P/2, P/2 }; + + + + +intPair getSpeechHint( uniqueID inObjectID, char *outSpeechFlip ) { + + intPair returnVal = defaultHint; + + + if( hintDir != NULL ) { + char *fileName = getHumanReadableString( inObjectID ); + + File *hintFile = hintDir->getChildFile( fileName ); + + if( hintFile->exists() ) { + + + int dataLength; + + unsigned char *data = + hintFile->readFileContents( &dataLength ); + + if( data != NULL ) { + if( dataLength == 9 ) { + int numUsed; + returnVal = readIntPair( data, dataLength, &numUsed ); + + *outSpeechFlip = data[8]; + } + delete [] data; + } + } + + delete [] fileName; + delete hintFile; + } + + return returnVal; + } + + + +void clearSpeechHint( uniqueID inObjectID ) { + + if( hintDir != NULL ) { + char *fileName = getHumanReadableString( inObjectID ); + + File *hintFile = hintDir->getChildFile( fileName ); + + hintFile->remove(); + + delete [] fileName; + delete hintFile; + } + + } + diff --git a/gameSource/speechHints.h b/gameSource/speechHints.h new file mode 100644 index 0000000..8868e3d --- /dev/null +++ b/gameSource/speechHints.h @@ -0,0 +1,22 @@ +// tracks speech hints for objects (last time speech offset was assigned +// for an object by a scene selection or user action). + +#include "uniqueID.h" +#include "common.h" + + + +// must be called after resourceManager init call +void initSpeechHints(); +void freeSpeechHints(); + + + +// inID uses inUsesID +void setSpeechHint( uniqueID inObjectID, + intPair inSpeechOffset, char inSpeechflip ); + +intPair getSpeechHint( uniqueID inObjectID, char *outSpeechFlip ); + + +void clearSpeechHint( uniqueID inObjectID ); diff --git a/gameSource/stack.png b/gameSource/stack.png new file mode 100644 index 0000000..c131a1c Binary files /dev/null and b/gameSource/stack.png differ diff --git a/gameSource/stamp.png b/gameSource/stamp.png new file mode 100644 index 0000000..78a1405 Binary files /dev/null and b/gameSource/stamp.png differ diff --git a/gameSource/templates/footer.php b/gameSource/templates/footer.php new file mode 100644 index 0000000..e492cbb --- /dev/null +++ b/gameSource/templates/footer.php @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/gameSource/templates/header.php b/gameSource/templates/header.php new file mode 100644 index 0000000..7fb4f26 --- /dev/null +++ b/gameSource/templates/header.php @@ -0,0 +1,12 @@ + + + +Sleep Is Death Flip Book + + + + + +

    +
    \ No newline at end of file diff --git a/gameSource/templates/index.html b/gameSource/templates/index.html new file mode 100644 index 0000000..62d2eb4 --- /dev/null +++ b/gameSource/templates/index.html @@ -0,0 +1,41 @@ + + + +Sleep Is Death Flip Book + + + + + +
    +
    + + + + + + + + + + + + +
    + + +
    + + + +
    + + + + +
    + + + + \ No newline at end of file diff --git a/gameSource/templates/index.php b/gameSource/templates/index.php new file mode 100644 index 0000000..f7929f1 --- /dev/null +++ b/gameSource/templates/index.php @@ -0,0 +1,125 @@ +"; + +// clicking on image goes to next +if( $next != -1 ) { + echo ""; + } +echo ""; +if( $next != -1 ) { + echo ""; + } + +echo ""; + + +echo ""; + +echo ""; +if( $prev != -1 ) { + echo "". + ""; + } +echo ""; + +echo ""; +if( $next != -1 ) { + echo "". + ""; + } +echo ""; + +echo ""; + + +// preload next frame's image to speed loading... make it tiny and unobtrusive + +if( $next != -1 ) { + echo ""; + } + + + +include( "footer.php" ); + +?> \ No newline at end of file diff --git a/gameSource/templates/next.png b/gameSource/templates/next.png new file mode 100644 index 0000000..79875e8 Binary files /dev/null and b/gameSource/templates/next.png differ diff --git a/gameSource/templates/nextButton.html b/gameSource/templates/nextButton.html new file mode 100644 index 0000000..b616d07 --- /dev/null +++ b/gameSource/templates/nextButton.html @@ -0,0 +1,2 @@ + + diff --git a/gameSource/templates/preloadNext.html b/gameSource/templates/preloadNext.html new file mode 100644 index 0000000..cf178f3 --- /dev/null +++ b/gameSource/templates/preloadNext.html @@ -0,0 +1 @@ +
    diff --git a/gameSource/templates/prev.png b/gameSource/templates/prev.png new file mode 100644 index 0000000..dd563d1 Binary files /dev/null and b/gameSource/templates/prev.png differ diff --git a/gameSource/templates/prevButton.html b/gameSource/templates/prevButton.html new file mode 100644 index 0000000..fc66adf --- /dev/null +++ b/gameSource/templates/prevButton.html @@ -0,0 +1,2 @@ + + diff --git a/gameSource/templates/x.html b/gameSource/templates/x.html new file mode 100644 index 0000000..b984c48 --- /dev/null +++ b/gameSource/templates/x.html @@ -0,0 +1,41 @@ + + + +Sleep Is Death Flip Book + + + + + +
    +
    + + + + + + + + + + + + +
    + + +
    +#PREV + +#NEXT +
    + + +#PRELOAD + +
    + + + + \ No newline at end of file diff --git a/gameSource/testUsage.cpp b/gameSource/testUsage.cpp new file mode 100644 index 0000000..bb2def6 --- /dev/null +++ b/gameSource/testUsage.cpp @@ -0,0 +1,20 @@ +#include "resourceManager.h" +#include "resourceDatabase.h" +#include "usageDatabase.h" + +#include + + +int main() { + + // init this first so it creates dir where stringDB can go. + initResourceManager(); + initDatabase(); + + for( int i=0; i<5; i++ ) { + initUsageDatabase(); + freeUsageDatabase(); + } + exit( 0 ); + + } diff --git a/gameSource/timeMark.png b/gameSource/timeMark.png new file mode 100644 index 0000000..636a954 Binary files /dev/null and b/gameSource/timeMark.png differ diff --git a/gameSource/trans.png b/gameSource/trans.png new file mode 100644 index 0000000..1865736 Binary files /dev/null and b/gameSource/trans.png differ diff --git a/gameSource/transSmall.png b/gameSource/transSmall.png new file mode 100644 index 0000000..7ecd59b Binary files /dev/null and b/gameSource/transSmall.png differ diff --git a/gameSource/undo.png b/gameSource/undo.png new file mode 100644 index 0000000..b7bae99 Binary files /dev/null and b/gameSource/undo.png differ diff --git a/gameSource/uniqueID.cpp b/gameSource/uniqueID.cpp new file mode 100644 index 0000000..8ba872c --- /dev/null +++ b/gameSource/uniqueID.cpp @@ -0,0 +1,191 @@ + +#include "uniqueID.h" + +#include "minorGems/crypto/hashes/sha1.h" +#include "minorGems/util/log/AppLog.h" + + +#include +#include + + +uniqueID defaultID = { { 0, 0, 0, + 0, 0, 0 } }; + + + + + +uniqueID makeUniqueID( unsigned char *inData, int inLength ) { + + uniqueID returnValue; + + + unsigned char *hash = computeRawSHA1Digest( inData, inLength ); + + // only first U bytes of hash used + memcpy( returnValue.bytes, hash, U ); + + + delete [] hash; + + + return returnValue; + } + + + +partialUniqueID startUniqueID() { + SHA_CTX ctx; + SHA1_Init( &ctx ); + return ctx; + } + + +partialUniqueID addToUniqueID( partialUniqueID inPartial, + unsigned char *inData, int inLength ) { + // update overwites data + unsigned char *buffer = new unsigned char[inLength]; + memcpy( buffer, inData, inLength ); + + SHA1_Update( &inPartial, buffer, inLength ); + + delete [] buffer; + + return inPartial; + } + + +uniqueID makeUniqueID( partialUniqueID inPartial ) { + uniqueID returnValue; + + unsigned char resultBuffer[ SHA1_DIGEST_LENGTH ]; + + SHA1_Final( resultBuffer, &inPartial ); + + memcpy( returnValue.bytes, resultBuffer, U ); + + return returnValue; + } + + + + +uniqueID readUniqueID( unsigned char *inBytes, int inLength, + int *outNumUsed ) { + uniqueID returnValue; + + if( inLength >= U ) { + memcpy( returnValue.bytes, inBytes, U ); + *outNumUsed = U; + } + else { + AppLog::error( + "ERROR: not enough bytes in data string for a uniqueID\n" ); + *outNumUsed = -1; + } + + return returnValue; + } + + + +char readUniqueIDs( uniqueID *outDest, int inNumToRead, + unsigned char *inBytes, int inLength, + int *outNumUsed ) { + + *outNumUsed = 0; + + for( int i=0; ibytes, raw, U ); + + delete [] raw; + + return true; + } + + + + + diff --git a/gameSource/uniqueID.h b/gameSource/uniqueID.h new file mode 100644 index 0000000..67cc6b1 --- /dev/null +++ b/gameSource/uniqueID.h @@ -0,0 +1,62 @@ +#ifndef UNIQUE_ID_INCLUDED +#define UNIQUE_ID_INCLUDED + +// length of a unique ID (only first 6 bytes of SHA1 needed) +#define U 6 + + +typedef struct uniqueID { + unsigned char bytes[U]; + } uniqueID; + + + +extern uniqueID defaultID; + + + +uniqueID makeUniqueID( unsigned char *inData, int inLength ); + + + +#include "minorGems/crypto/hashes/sha1.h" + +typedef SHA_CTX partialUniqueID; + + +// for building a unique ID from several chunks of data +partialUniqueID startUniqueID(); +partialUniqueID addToUniqueID( partialUniqueID inPartial, + unsigned char *inData, int inLength ); +uniqueID makeUniqueID( partialUniqueID inPartial ); + + +// reads a unique ID from a data string +uniqueID readUniqueID( unsigned char *inBytes, int inLength, + int *outNumUsed ); + + +// reads an array of unique IDs from a data string +// stored in space pointed to by outDest +// returns true on success +char readUniqueIDs( uniqueID *outDest, int inNumToRead, + unsigned char *inBytes, int inLength, + int *outNumUsed); + + +char equal( uniqueID inA, uniqueID inB ); + + +// gets ID in hex as a \0-terminated string +// destroyed by caller +char *getHumanReadableString( uniqueID inID ); + + +// returns true on success +char parseHumanReadableString( char *inString, uniqueID *outResult ); + + + +#endif + + diff --git a/gameSource/unzoom.png b/gameSource/unzoom.png new file mode 100644 index 0000000..cd16e91 Binary files /dev/null and b/gameSource/unzoom.png differ diff --git a/gameSource/usageDatabase.cpp b/gameSource/usageDatabase.cpp new file mode 100644 index 0000000..5b6dc3a --- /dev/null +++ b/gameSource/usageDatabase.cpp @@ -0,0 +1,439 @@ +// database tracking which resources use which other resources + +#include "usageDatabase.h" +#include "resourceDatabase.h" +#include "common.h" +#include "minorGems/util/log/AppLog.h" +#include "minorGems/util/SimpleVector.h" + +#include "Scene.h" +#include "StateObject.h" +#include "SpriteResource.h" +#include "Room.h" +#include "Tile.h" +#include "Song.h" +#include "Scale.h" +#include "Music.h" +#include "TimbreResource.h" + + + +// for platform-independent ptr -> unsigned int casting +#include + + +typedef struct usageRecord { + uniqueID id; + + SimpleVector usedByList; + SimpleVector usesList; + } usageRecord; + + + + +#define B 2000 + +class UsageRecordHashTable { + public: + ~UsageRecordHashTable(); + + void insert( usageRecord *inRecord ); + + // does not destroy record + void remove( usageRecord *inRecord ); + + usageRecord *lookup( uniqueID inID ); + + protected: + + SimpleVector mBins[B]; + + }; + + +UsageRecordHashTable::~UsageRecordHashTable() { + for( int b=0; b *v = &( mBins[b] ); + + int numHits = v->size(); + + for( int i=0; igetElementFast( i ) ); + } + } + } + + +// use first 4 ID bytes as an int +#define getHashBin( id ) \ + (uintptr_t)( id.bytes[0] | \ + id.bytes[1] << 8 | \ + id.bytes[2] << 16 | \ + id.bytes[3] << 24 ) % B + + +void UsageRecordHashTable::insert( usageRecord *inRecord ) { + + int b = getHashBin( inRecord->id ); + + mBins[b].push_back( inRecord ); + } + + + +void UsageRecordHashTable::remove( usageRecord *inRecord ) { + int b = getHashBin( inRecord->id ); + + mBins[b].deleteElementEqualTo( inRecord ); + } + + + +usageRecord *UsageRecordHashTable::lookup( uniqueID inID ) { + int b = getHashBin( inID ); + + SimpleVector *v = &( mBins[b] ); + + int numHits = v->size(); + + for( int i=0; igetElementFast( i ) ); + + if( equal( record->id, inID ) ) { + return record; + } + } + + return NULL; + } + + + +static usageRecord *addUsage( uniqueID inID, + uniqueID inUsesID, usageRecord *inIDRecord ); + + + +static UsageRecordHashTable *table; + + +void initUsageDatabase() { + table = new UsageRecordHashTable(); + + + printf( "Building usage database\n" ); + int addUsageCalls = 0; + + // scenes use objects and rooms + + int numScenes = countSearchResults( "scene", "" ); + uniqueID *scenes = new uniqueID[ numScenes ]; + + int numGotten = getSearchResults( "scene", "", 0, numScenes, scenes ); + + numScenes = numGotten; + + for( int i=0; ilookup( inID ); + } + + if( r == NULL ) { + //printf( "Adding new record\n" ); + r = new usageRecord; + r->id = inID; + + table->insert( r ); + + } + r->usesList.push_back( inUsesID ); + // allow duplicates, because checking for them is too slow + /* + else { + // record exists. Make sure this usage not already tracked + //printf( "Record exists\n" ); + + int numUses = r->usesList.size(); + + char found = false; + for( int i=0; iusesList.getElementFast( i ) ) ) ) { + found = true; + } + } + if( !found ) { + // new addition + r->usesList.push_back( inUsesID ); + } + } + */ + usageRecord *returnValue = r; + + + r = table->lookup( inUsesID ); + + if( r == NULL ) { + r = new usageRecord; + r->id = inUsesID; + + table->insert( r ); + } + r->usedByList.push_back( inID ); + // allow duplicates, because checking for them is too slow + /* + else { + // record exists. Make sure this usage not already tracked + + int numUsedBy = r->usedByList.size(); + + char found = false; + for( int i=0; iusedByList.getElementFast( i ) ) ) ) { + found = true; + } + } + if( !found ) { + // new addition + r->usedByList.push_back( inID ); + } + } + */ + return returnValue; + } + + + + +void addUsage( uniqueID inID, + uniqueID inUsesID ) { + addUsage( inID, inUsesID, NULL ); + } + + + + +// remove all usages associated with inID +// note that because inID is a hash of the using object, removing one +// usage from that object means that the ID of the object will change +// Thus, we would never be removing a usage from inID unless we were deleting +// inID entirely, which means we'd want to remove ALL usages from inID +void removeUsages( uniqueID inID ) { + // first, make sure it's not used by anything + + usageRecord *r = table->lookup( inID ); + + if( r != NULL ) { + + if( r->usedByList.size() == 0 ) { + + // walk over all resources that inID uses + int numUses = r->usesList.size(); + + for( int i=0; iusesList.getElementFast( i ); + + + // remove inID from each of these IDs' usedBy lists + usageRecord *r2 = table->lookup( *usesID ); + + if( r2 != NULL ) { + + // might contain duplicates, so check all + for( int j=0; jusedByList.size(); j++ ) { + + if( equal( + inID, + *( r2->usedByList.getElementFast( j ) ) ) ) { + + r2->usedByList.deleteElement( j ); + j--; + } + + } + + + } + + + } + + table->remove( r ); + + delete r; + } + else { + AppLog::error( "Error: Trying to remove a resource that is " + "still used" ); + } + } + + } + + + + +char isUsed( uniqueID inID ) { + usageRecord *r = table->lookup( inID ); + + if( r != NULL ) { + + if( r->usedByList.size() > 0 ) { + return true; + } + } + return false; + } + + + +uniqueID getUser( uniqueID inID ) { + usageRecord *r = table->lookup( inID ); + + if( r != NULL ) { + + if( r->usedByList.size() > 0 ) { + + return *( r->usedByList.getElement( 0 ) ); + } + } + return defaultID; + } + + + + diff --git a/gameSource/usageDatabase.h b/gameSource/usageDatabase.h new file mode 100644 index 0000000..441baf8 --- /dev/null +++ b/gameSource/usageDatabase.h @@ -0,0 +1,29 @@ +// database tracking which resources use which other resources + +#include "uniqueID.h" + + + +void initUsageDatabase(); +void freeUsageDatabase(); + + + +// inID uses inUsesID +void addUsage( uniqueID inID, + uniqueID inUsesID ); + +// remove all usages associated with inID +// note that because inID is a hash of the using object, removing one +// usage from that object means that the ID of the object will change +// Thus, we would never be removing a usage from inID unless we were deleting +// inID entirely, which means we'd want to remove ALL usages from inID +void removeUsages( uniqueID inID ); + + + +char isUsed( uniqueID inID ); + + +// if isUsed returns true, returns ID of one of the resources using inID +uniqueID getUser( uniqueID inID ); diff --git a/gameSource/verLine.png b/gameSource/verLine.png new file mode 100644 index 0000000..664ea56 Binary files /dev/null and b/gameSource/verLine.png differ diff --git a/gameSource/walls.png b/gameSource/walls.png new file mode 100644 index 0000000..7c51768 Binary files /dev/null and b/gameSource/walls.png differ diff --git a/gameSource/warning.png b/gameSource/warning.png new file mode 100644 index 0000000..3b8db73 Binary files /dev/null and b/gameSource/warning.png differ diff --git a/gameSource/zoom.png b/gameSource/zoom.png new file mode 100644 index 0000000..0d12a1a Binary files /dev/null and b/gameSource/zoom.png differ