diff --git a/.gitignore b/.gitignore index cd8c562..9567a8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # HaxeFlixel .haxelib/ export/ -dump/ temp/ # macOS diff --git a/.prettierignore b/.prettierignore index 126f760..8ba024c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,5 @@ # Ignore artifacts -export/ +.haxelib/ dump/ +export/ temp/ diff --git a/COMPILING.md b/COMPILING.md index 9136560..e23e352 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -31,6 +31,9 @@ These are the necessary steps required to compile on the game on __***every***__ 8. Run `hmm install` to start installing all of the game's dependencies. **This will take a bit, so be patient**. +> [!TIP] +> If the libraries do not install correctly, then you can run the `.bat` file or `.sh` file (according to your system). + 9. Run `haxelib run lime setup` to setup the lime command. - This will allow you to compile and run the game on many common platforms, such as every major desktop platform (Windows, macOS, Linux, etc.), both popular mobile systems (Android and iOS), and more. @@ -40,6 +43,9 @@ These are the necessary steps required to compile on the game on __***every***__ > [!TIP] > You can replace `html5` with other platforms to compile it accordingly. +> [!IMPORTANT] +> If ever, for some reason, the shaders make the screen become black, it is advised you clear your computer's `Temp/` directory. If the issue persists, then please take a look at the [contributing](CONTRIBUTING.md) file for info on how to report a bug. + ## Extra Steps (for Running and Configuring the Game on Other Platforms) ### Windows diff --git a/assets/shaders/ntsc.frag b/assets/shaders/ntsc.frag new file mode 100644 index 0000000..8c66061 --- /dev/null +++ b/assets/shaders/ntsc.frag @@ -0,0 +1,193 @@ +#pragma header + +#pragma format R8G8B8A8_SRGB + +#define NTSC_CRT_GAMMA 2.5 +#define NTSC_MONITOR_GAMMA 2.0 + +#define TWO_PHASE +#define COMPOSITE +//#define THREE_PHASE +// #define SVIDEO + +// begin params +#define PI 3.14159265 + +#if defined(TWO_PHASE) + #define CHROMA_MOD_FREQ (4.0 * PI / 15.0) +#elif defined(THREE_PHASE) + #define CHROMA_MOD_FREQ (PI / 3.0) +#endif + +#if defined(COMPOSITE) + #define SATURATION 1.0 + #define BRIGHTNESS 1.0 + #define ARTIFACTING 1.0 + #define FRINGING 1.0 +#elif defined(SVIDEO) + #define SATURATION 1.0 + #define BRIGHTNESS 1.0 + #define ARTIFACTING 0.0 + #define FRINGING 0.0 +#endif +// end params + +uniform int uFrame; +uniform float uInterlace; + +// fragment compatibility #defines + +#if defined(COMPOSITE) || defined(SVIDEO) +mat3 mix_mat = mat3( + BRIGHTNESS, FRINGING, FRINGING, + ARTIFACTING, 2.0 * SATURATION, 0.0, + ARTIFACTING, 0.0, 2.0 * SATURATION +); +#endif + +// begin ntsc-rgbyuv +const mat3 yiq2rgb_mat = mat3( + 1.0, 0.956, 0.6210, + 1.0, -0.2720, -0.6474, + 1.0, -1.1060, 1.7046); + +vec3 yiq2rgb(vec3 yiq) +{ + return yiq * yiq2rgb_mat; +} + +const mat3 yiq_mat = mat3( + 0.2989, 0.5870, 0.1140, + 0.5959, -0.2744, -0.3216, + 0.2115, -0.5229, 0.3114 +); + +vec3 rgb2yiq(vec3 col) +{ + return col * yiq_mat; +} +// end ntsc-rgbyuv + +#define TAPS 32 +const float luma_filter[TAPS + 1] = float[TAPS + 1]( + -0.000174844, + -0.000205844, + -0.000149453, + -0.000051693, + 0.000000000, + -0.000066171, + -0.000245058, + -0.000432928, + -0.000472644, + -0.000252236, + 0.000198929, + 0.000687058, + 0.000944112, + 0.000803467, + 0.000363199, + 0.000013422, + 0.000253402, + 0.001339461, + 0.002932972, + 0.003983485, + 0.003026683, + -0.001102056, + -0.008373026, + -0.016897700, + -0.022914480, + -0.021642347, + -0.008863273, + 0.017271957, + 0.054921920, + 0.098342579, + 0.139044281, + 0.168055832, + 0.178571429); + +const float chroma_filter[TAPS + 1] = float[TAPS + 1]( + 0.001384762, + 0.001678312, + 0.002021715, + 0.002420562, + 0.002880460, + 0.003406879, + 0.004004985, + 0.004679445, + 0.005434218, + 0.006272332, + 0.007195654, + 0.008204665, + 0.009298238, + 0.010473450, + 0.011725413, + 0.013047155, + 0.014429548, + 0.015861306, + 0.017329037, + 0.018817382, + 0.020309220, + 0.021785952, + 0.023227857, + 0.024614500, + 0.025925203, + 0.027139546, + 0.028237893, + 0.029201910, + 0.030015081, + 0.030663170, + 0.031134640, + 0.031420995, + 0.031517031); + +vec4 pass1(vec2 uv) +{ + vec2 fragCoord = uv * openfl_TextureSize; + + vec4 cola = texture2D(bitmap, uv).rgba; + vec3 yiq = rgb2yiq(cola.rgb); + + #if defined(TWO_PHASE) + float chroma_phase = PI * (mod(fragCoord.y, 2.0) + float(uFrame)); + #elif defined(THREE_PHASE) + float chroma_phase = 0.6667 * PI * (mod(fragCoord.y, 3.0) + float(uFrame)); + #endif + + float mod_phase = chroma_phase + fragCoord.x * CHROMA_MOD_FREQ; + + float i_mod = cos(mod_phase); + float q_mod = sin(mod_phase); + + if(uInterlace == 1.0) { + yiq.yz *= vec2(i_mod, q_mod); // Modulate. + yiq *= mix_mat; // Cross-talk. + yiq.yz *= vec2(i_mod, q_mod); // Demodulate. + } + return vec4(yiq, cola.a); +} + +vec4 fetch_offset(vec2 uv, float offset, float one_x) { + return pass1(uv + vec2((offset - 0.5) * one_x, 0.0)).xyzw; +} + +void main() +{ + vec2 uv = openfl_TextureCoordv; + vec2 fragCoord = uv * openfl_TextureSize; + + float one_x = 1.0 / openfl_TextureSize.x; + vec4 signal = vec4(0.0); + + for (int i = 0; i < TAPS; i++) + { + float offset = float(i); + + vec4 sums = fetch_offset(uv, offset - float(TAPS), one_x) * 2; + + signal += sums * vec4(luma_filter[i], chroma_filter[i], chroma_filter[i], 1.0); + } + signal += pass1(uv - vec2(0.5 / openfl_TextureSize.x, 0.0)).xyzw * + vec4(luma_filter[TAPS], chroma_filter[TAPS], chroma_filter[TAPS], 1.0); + + vec3 rgb = yiq2rgb(signal.xyz); + gl_FragColor = vec4(pow(rgb, vec3(NTSC_CRT_GAMMA / NTSC_MONITOR_GAMMA)), flixel_texture2D(bitmap, uv).a); +} \ No newline at end of file diff --git a/assets/shaders/skew.frag b/assets/shaders/skew.frag new file mode 100644 index 0000000..1448043 --- /dev/null +++ b/assets/shaders/skew.frag @@ -0,0 +1,29 @@ +//SHADERTOY PORT FIX +#pragma header +vec2 uv = openfl_TextureCoordv.xy; +vec2 fragCoord = openfl_TextureCoordv*openfl_TextureSize; +vec2 iResolution = openfl_TextureSize; +uniform float iTime; +#define iChannel0 bitmap +#define texture flixel_texture2D +#define fragColor gl_FragColor +#define mainImage main +//SHADERTOY PORT FIX + +uniform float skew = 0.0; + + + +float lerpp(float a, float b, float t){ + return a + (b - a) * t; +} +void main(void){ + float dfb = uv.y; + vec4 c = vec4(0.0,0.0,0.0,0.0); + vec2 pos = uv; + pos.x = uv.x+(skew*dfb); + if(pos.x > 0 && pos.x < 1){ + gl_FragColor = flixel_texture2D( bitmap, pos); + } + +} diff --git a/assets/shaders/vcrlines.frag b/assets/shaders/vcrlines.frag new file mode 100644 index 0000000..25d99bc --- /dev/null +++ b/assets/shaders/vcrlines.frag @@ -0,0 +1,81 @@ +#pragma header + +#ifdef GL_ES +precision mediump float; +#endif + +uniform float time; +uniform vec2 resolution; + +float pi = 3.14159265359; +float curvature = 0.065; +float vignetteStrength = 0.4; +float theScanLine = 0.0; + +vec2 curve(vec2 inp) +{ + inp.x = inp.x - sin(inp.y * pi) * curvature * (inp.x - 0.5); + inp.y = inp.y - sin(inp.x * pi) * curvature * (inp.y - 0.5); + return inp; +} + +vec2 zoomOut(vec2 inp) +{ + float zoom = 1.0 + 1.3 * curvature; + return vec2(0.5, 0.5) + ((inp - vec2(0.5, 0.5)) * zoom); +} + +float lerp(float a, float b, float c) +{ + return a + c * (b - a); +} + +vec4 vignette(vec2 inp) +{ + float t = 0.0; + t = lerp(0.5, vignetteStrength * distance(inp, vec2(0.5, 0.5)), 0.98); + return vec4(t, t, t, 1.0); +} + +vec4 staticc(vec2 inp) +{ + float t = 0.0; + t = cos(inp.y * resolution.y) * 2.0; + + float val = sin(100.0 + time * cos(100.0 + time) * 2.0) * 14.0 * inp.y; + val = clamp(val, -1.55, 1.55); + t += tan(val) * 0.05; + + return vec4(t, t, t, 1.0); +} + +bool inRect(vec2 rect, vec2 rectDim, vec2 inp) +{ + vec2 clamped = clamp(inp, rect, rectDim); + return (clamped.x == inp.x && clamped.y == inp.y); +} + +void main(void) +{ + vec2 uv = zoomOut(curve(openfl_TextureCoordv.xy)); + vec4 col; + theScanLine = sin(time * pi + uv.y) + tan(time - uv.y * uv.y) - cos(uv.y); + + if (inRect(vec2(0.0, 0.0), vec2(1.0, 1.0), uv)) + { + if (inRect(vec2(0.0, theScanLine), vec2(1.0, theScanLine + 0.1), uv)) + uv.x -= sin(theScanLine - uv.y) * 0.04; // Scanline distortion is determined by the last number + + // FIXED: use `bitmap` and `texture` + col = texture(bitmap, uv); + + col -= vignette(uv); + col += staticc(uv) * 0.015; + } + else + { + col = vec4(0.0, 0.0, 0.0, 1.0); + } + + gl_FragColor = col; +} diff --git a/project.hxp b/project.hxp index 09ae297..12adfde 100644 --- a/project.hxp +++ b/project.hxp @@ -16,850 +16,850 @@ using StringTools; @:nullSafety class Project extends HXProject { - // - // METADATA - // =========================== - - /** - * The game's version number, as a Semantic Versioning string with no prefix. - * REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES! - * You only have to change it here, the rest of the game will query this value. - */ - static final VERSION:String = '0.1.0-demo'; - - /** - * The game's name. Used as the default window title. - */ - static final TITLE:String = 'Starcore'; - - /** - * The name of the generated executable file. - * For example, `STARCORE` will create a file called `STARCORE.exe`. - */ - static final EXECUTABLE_NAME:String = 'STARCORE'; - - /** - * The relative location of the source code. - */ - static final SOURCE_DIR:String = 'source'; - - /** - * The fully qualified class path for the game's preloader. - * Particularly important on HTML5 but it's used on all platforms. - */ - static final PRELOADER:String = 'flixel.system.FlxPreloader'; - - /** - * A package name used for identifying the app on various app stores. - */ - static final PACKAGE_NAME:String = 'com.bitmapstudios.starcore'; - - /** - * The fully qualified class path for the entry point class to execute when launching the game. - * It's where `public static function main():Void` goes. - */ - static final MAIN_CLASS:String = 'Main'; - - /** - * The company name for the game. - * This appears as metadata in many places. - */ - static final COMPANY:String = 'BitMap Studios'; - - /** - * Path to the Haxe script run before building the game. - */ - // static final PREBUILD_HX:String = 'source/Prebuild.hx'; - /** - * Path to the Haxe script run after building the game. - */ - // static final POSTBUILD_HX:String = 'source/Postbuild.hx'; - - /** - * Asset path globs to always exclude from asset libraries. - */ - static final EXCLUDE_ASSETS:Array = ['.*', 'cvs', 'thumbs.db', 'desktop.ini', '*.hash', '*.md']; - - /** - * Asset path globs to exclude on web platforms. - */ - static final EXCLUDE_ASSETS_WEB:Array = ['*.ogg']; - - /** - * Asset path globs to exclude on native platforms. - */ - static final EXCLUDE_ASSETS_NATIVE:Array = ['*.mp3']; - - // - // FEATURE FLAGS - // ========================================== - - /** - * Allows the game to be displayed in the user's Discord "Activity" box - * (it can still be disabled by the user in-game if the option is off). - */ - static final DISCORD_RPC_ALLOWED:FeatureFlag = 'DISCORD_RPC_ALLOWED'; - - /** - * Enables the game to use advanced shaders, giving it a more unsettling and unnerving vibe. - * Sort of like the analog horror videos you see on YouTube. - */ - static final ADVANCED_SHADERS_ALLOWED:FeatureFlag = 'ADVANCED_SHADERS_ALLOWED'; - - /** - * Allows the developer(s) to use built-in editors, - * such as creating animations for entities. - */ - static final DEBUG_EDITORS_ALLOWED:FeatureFlag = 'DEBUG_EDITORS_ALLOWED'; - - /** - * Permits the game to use a built-in logging system, which writes all - * current logs to a new text file inside of a folder called `logs`, - * located in the games output folder. - */ - static final LOGGING_ALLOWED:FeatureFlag = 'LOGGING_ALLOWED'; - - /** - * Enables the game to programmatically change sounds and add - * all kinds of effects to them. - */ - static final SOUND_FILTERS_ALLOWED:FeatureFlag = 'SOUND_FILTERS_ALLOWED'; - - public function new() - { - super(); - - flair(); - configureApp(); - - displayTarget(); - configureOutputDir(); - configureFeatureFlags(); - configureCompileDefines(); - configureHaxelibs(); - configureAssets(); - configureIcons(); // TODO: Implement code when icons are made! - } - - /** - * Display some fancy info before initializing. - */ - function flair() - { - info('STARCORE'); - info('Setting up build...'); - info('Target Version: ' + VERSION); - info('Git Branch: ' + getGitBranch()); - info('Git Commit: ' + getGitCommit()); - info('Git Modified? ' + getGitModified()); - info('Display? ' + isDisplay()); - logSeparator(); - } - - /** - * Apply basic project metadata, such as the game title and version number, - * as well as info like the package name and company (used by various app stores). - */ - function configureApp() - { - // - // METADATA - // ====================================== - this.meta.title = TITLE; - this.meta.version = VERSION; - this.meta.packageName = PACKAGE_NAME; - this.meta.company = COMPANY; - this.meta.description = 'A space themed, open world survival sandbox game.'; - this.meta.companyId = COMPANY; - this.meta.companyUrl = COMPANY; - - // - // CODE - // ================================= - this.sources.push(SOURCE_DIR); // Source code location (more than one can be added) - - // - // PREBUILD & POSTBUILD - // ================================================== - // this.preBuildCallbacks.push(buildHaxeCLICommand(PREBUILD_HX)); - // this.postBuildCallbacks.push(buildHaxeCLICommand(POSTBUILD_HX)); - - // - // APP - // ========================== - this.app.main = MAIN_CLASS; - this.app.preloader = PRELOADER; - this.app.file = EXECUTABLE_NAME; - // These values are only used by the SWF target - // this.app.path - // this.app.init - // this.app.swfVersion - // this.app.url - - // - // WINDOW - // ===================================== - this.window.fps = 60; - this.window.width = (!isMobile()) ? 960 : 0; - this.window.height = (!isMobile()) ? 720 : 0; - this.window.background = 0xFF000000; - this.window.fullscreen = false; - this.window.resizable = isWeb(); - this.window.orientation = (isDesktop() || isMobile()) ? Orientation.LANDSCAPE : Orientation.AUTO; - this.window.hardware = true; - this.window.vsync = false; - this.window.allowHighDPI = true; // Force / allow high DPI - } - - /** - * Log information about the configured target platform. - */ - function displayTarget() - { - // Display the target operating system - switch (this.target) - { - case Platform.WINDOWS: - info('Target Platform: Windows'); - case Platform.MAC: - info('Target Platform: MacOS'); - case Platform.LINUX: - info('Target Platform: Linux'); - case Platform.ANDROID: - info('Target Platform: Android'); - case Platform.IOS: - info('Target Platform: IOS'); - case Platform.HTML5: - info('Target Platform: HTML5'); - // See lime.tools.Platform for a full list - // case Platform.EMSCRITEN: - // case Platform.AIR: - // case Platform.BLACKBERRY: - // case Platform.CONSOLE_PC: - // case Platform.FIREFOX: - // case Platform.FLASH: - // case Platform.PS3: - // case Platform.PS4: - // case Platform.TIZEN: - // case Platform.TVOS: - // case Platform.VITA: - // case Platform.WEBOS: - // case Platform.WIIU: - // case Platform.XBOX1: - default: - error('Unsupported platform (got ${target})'); - } - - switch (this.platformType) - { - case PlatformType.DESKTOP: - info('Target Platform Type: Desktop'); - case PlatformType.MOBILE: - info('Target Platform Type: Mobile'); - case PlatformType.WEB: - info('Target Platform Type: Web'); - case PlatformType.CONSOLE: - info('Target Platform Type: Console'); - default: - error('Unknown target platform type (got ${platformType})'); - } - - // Print whether we are using HXCPP, HashLink, or something else. - if (isWeb()) - { - info('Target Language: JavaScript (HTML5)'); - } - else if (isHashLink()) - { - info('Target Language: HashLink'); - } - else if (isNeko()) - { - info('Target Language: Neko'); - } - else if (isJava()) - { - info('Target Language: Java'); - } - else if (isNodeJS()) - { - info('Target Language: JavaScript (NodeJS)'); - } - else if (isCSharp()) - { - info('Target Language: C#'); - } - else if (isCPlusPlus()) - { - info('Target Language: C++'); - } - else - { - info('Target Language: Unknown'); - } - - for (arch in this.architectures) - { - // Display the list of target architectures. - switch (arch) - { - case Architecture.X86: - info('Architecture: x86'); - case Architecture.X64: - info('Architecture: x64'); - case Architecture.ARMV5: - info('Architecture: ARMv5'); - case Architecture.ARMV6: - info('Architecture: ARMv6'); - case Architecture.ARMV7: - info('Architecture: ARMv7'); - case Architecture.ARMV7S: - info('Architecture: ARMv7S'); - case Architecture.ARM64: - info('Architecture: ARMx64'); - case Architecture.MIPS: - info('Architecture: MIPS'); - case Architecture.MIPSEL: - info('Architecture: MIPSEL'); - case null: - if (!isWeb()) - { - error('Unsupported architecture (got null on non-web platform)'); - } - else - { - info('Architecture: Web'); - } - default: - error('Unsupported architecture (got ${arch})'); - } - } - } - - /** - * Apply various feature flags based on the target platform and the user-provided build flags. - */ - function configureFeatureFlags() - { - // Enable Discord rich presence - // (works only for Windows). - DISCORD_RPC_ALLOWED.apply(this, isWindows()); - - // Enable dope ass screen filters - // (if the platform is on desktop). - ADVANCED_SHADERS_ALLOWED.apply(this, isDesktop()); - - // Enable the editors if the game is in debug mode, - // otherwise, disable them of course. - DEBUG_EDITORS_ALLOWED.apply(this, isDesktop() && isDebug()); - - // Enables dope ass logging - // (only works on desktop). - LOGGING_ALLOWED.apply(this, isDesktop()); - - // Allows the game to programmatically change sounds - // (only works on desktop). - SOUND_FILTERS_ALLOWED.apply(this, isDesktop()); - - logSeparator(); - } - - /** - * Set compilation flags which are not feature flags. - */ - function configureCompileDefines() - { - // Enable OpenFL's error handler. - // Required for the crash logger. - setHaxedef('openfl-enable-handle-error'); - - // Enable stack trace tracking. Good for debugging but has a (minor) performance impact. - setHaxedef('HXCPP_CHECK_POINTER'); - setHaxedef('HXCPP_STACK_LINE'); - setHaxedef('HXCPP_STACK_TRACE'); - setHaxedef('hscriptPos'); - - setHaxedef('safeMode'); - - // Disable the built in pause screen when unfocusing the game. - setHaxedef('FLX_NO_FOCUS_LOST_SCREEN'); - - // Disable the Flixel debugger entirely, since it - // messes with the filters sometimes. - setHaxedef('FLX_NO_DEBUG'); - - if (isRelease()) - { - // Improve performance on Nape. - setHaxedef('NAPE_RELEASE_BUILD'); - } - - // Cleaner looking compiler errors. - setHaxedef('message.reporting', 'pretty'); - } - - function configureOutputDir() - { - // Set the output directory. - // Depends on the target platform and build type. - var buildDir = 'export/${isDebug() ? 'debug' : isRelease() ? 'release' : 'test'}'; - buildDir += '/'; - info('Output Directory: $buildDir'); - app.path = buildDir; - logSeparator(); - } - - function configureHaxelibs() - { - // - // FLIXEL - // ================== - - // Convert the game to run on other platforms. - addHaxelib('lime'); - // Many frontend and backend utilities for the game's display. - addHaxelib('openfl'); - // Core game library. - addHaxelib('flixel'); - // Additional utilities for Flixel. - addHaxelib('flixel-addons'); - // VSCode debug support. - if (isDebug()) - { - addHaxelib('hxcpp-debug-server'); - } - - // - // CUSTOM - // =================== - - // Manipulate sound effects programmatically. - if (SOUND_FILTERS_ALLOWED.isEnabled(this)) - { - addHaxelib('flxsoundfilters'); - } - - // Discord rich presence. - if (DISCORD_RPC_ALLOWED.isEnabled(this)) - { - addHaxelib('hxdiscord_rpc'); - } - } - - function configureAssets() - { - // Add font assets. - addAssetPath('assets/fonts', 'assets/fonts', 'default', ['*.ttf'], []); - - // Add entity assets. - addAssetPath('assets/entities/data', 'assets/entities/data', 'default', ['*.json'], []); - addAssetPath('assets/entities/textures', 'assets/entities/textures', 'default', ['*.png', '*.xml'], []); - - // Add tile assets. - addAssetPath('assets/tiles/data', 'assets/tiles/data', 'default', ['*.json'], []); - addAssetPath('assets/tiles/textures', 'assets/tiles/textures', 'default', ['*.png'], []); - - // Add shared image assets. - addAssetPath('assets/shared/images', 'assets/shared/images', 'default', ['*.png'], []); - - // Add shader frag assets. - addAssetPath('assets/shaders', 'assets/shaders', 'default', ['*.frag'], []); - - if (isWeb()) - { - // Add shared music and sound assets for web (.mp3). - addAssetPath('assets/shared/music', 'assets/shared/music', 'default', ['*.mp3'], ['*.ogg']); - addAssetPath('assets/shared/sounds', 'assets/shared/sounds', 'default', ['*.mp3'], ['*.ogg']); - } - else if (isDesktop()) - { - // Add shared music and sound assets for desktop (.ogg). - addAssetPath('assets/shared/music', 'assets/shared/music', 'default', ['*.ogg'], ['*.mp3']); - addAssetPath('assets/shared/sounds', 'assets/shared/sounds', 'default', ['*.ogg'], ['*.mp3']); - } - } - - /** - * Configure the application's favicon and executable icon. - */ - function configureIcons() {} - - // - // HELPER FUNCTIONS - // (Easy functions to make the code more readable.) - // ======================================================================== - - public function isWeb():Bool - { - return this.platformType == PlatformType.WEB; - } - - public function isMobile():Bool - { - return this.platformType == PlatformType.MOBILE; - } - - public function isDesktop():Bool - { - return this.platformType == PlatformType.DESKTOP; - } - - public function isConsole():Bool - { - return this.platformType == PlatformType.CONSOLE; - } - - public function is32Bit():Bool - { - return this.architectures.contains(Architecture.X86); - } - - public function is64Bit():Bool - { - return this.architectures.contains(Architecture.X64); - } - - public function isWindows():Bool - { - return this.target == Platform.WINDOWS; - } - - public function isMac():Bool - { - return this.target == Platform.MAC; - } - - public function isLinux():Bool - { - return this.target == Platform.LINUX; - } - - public function isAndroid():Bool - { - return this.target == Platform.ANDROID; - } - - public function isIOS():Bool - { - return this.target == Platform.IOS; - } - - public function isHashLink():Bool - { - return this.targetFlags.exists('hl'); - } - - public function isNeko():Bool - { - return this.targetFlags.exists('neko'); - } - - public function isJava():Bool - { - return this.targetFlags.exists('java'); - } - - public function isNodeJS():Bool - { - return this.targetFlags.exists('nodejs'); - } - - public function isCSharp():Bool - { - return this.targetFlags.exists('cs'); - } - - public function isCPlusPlus():Bool - { - return this.defines.exists('cpp'); // Why in defines and not target flags... - } - - public function isDisplay():Bool - { - return this.command == 'display'; - } - - public function isDebug():Bool - { - return this.debug; - } - - public function isRelease():Bool - { - return this.defines.exists('final'); - } - - public function getHaxedef(name:String):Null - { - return this.haxedefs.get(name); - } - - public function setHaxedef(name:String, ?value:String):Void - { - if (value == null) - value = ''; - - this.haxedefs.set(name, value); - } - - public function unsetHaxedef(name:String):Void - { - this.haxedefs.remove(name); - } - - public function getDefine(name:String):Null - { - return this.defines.get(name); - } - - public function hasDefine(name:String):Bool - { - return this.defines.exists(name); - } - - /** - * Add a library to the list of dependencies for the project. - * - * @param name The name of the library to add. - * @param version The version of the library to add. Optional. - */ - public function addHaxelib(name:String, version:String = ''):Void - { - this.haxelibs.push(new Haxelib(name, version)); - } - - /** - * Add a `haxeflag` to the project. - */ - public function addHaxeFlag(value:String):Void - { - this.haxeflags.push(value); - } - - /** - * Call a Haxe build macro. - */ - public function addHaxeMacro(value:String):Void - { - addHaxeFlag('--macro ${value}'); - } - - /** - * Add an icon to the project. - * - * @param icon The path to the icon. - * @param size The size of the icon. Optional. - */ - public function addIcon(icon:String, ?size:Int):Void - { - this.icons.push(new Icon(icon, size)); - } - - /** - * Add an asset to the game build. - * - * @param path The path the asset is located at. - * @param rename The path the asset should be placed. - * @param library The asset library to add the asset to. `null` = 'default' - * @param embed Whether to embed the asset in the executable. - */ - public function addAsset(path:String, ?rename:String, ?library:String, embed:Bool = false):Void - { - // path, rename, type, embed, setDefaults - var asset = new Asset(path, rename, null, embed, true); - @:nullSafety(Off) - { - asset.library = library ?? 'default'; - } - this.assets.push(asset); - } - - /** - * Add an entire path of assets to the game build. - * - * @param path The path the assets are located at. - * @param rename The path the assets should be placed. - * @param library The asset library to add the assets to. `null` = 'default' - * @param include An optional array to include specific asset names. - * @param exclude An optional array to exclude specific asset names. - * @param embed Whether to embed the assets in the executable. - */ - public function addAssetPath(path:String, ?rename:String, library:String, ?include:Array, ?exclude:Array, embed:Bool = false):Void - { - // Argument parsing. - if (path == '') - return; - - if (include == null) - include = []; - - if (exclude == null) - exclude = []; - - var targetPath = rename ?? path; - if (targetPath != '') - targetPath += '/'; - - // Validate path - if (!sys.FileSystem.exists(path)) - { - error('Could not find asset path "${path}".'); - } - else if (!sys.FileSystem.isDirectory(path)) - { - error('Could not parse asset path "${path}", expected a directory.'); - } - else - { - info('Adding asset path ${path}...'); - } - - for (file in sys.FileSystem.readDirectory(path)) - { - if (sys.FileSystem.isDirectory('${path}/${file}')) - { - // Attempt to recursively add all assets in the directory - if (this.filter(file, ['*'], exclude)) - { - addAssetPath('${path}/${file}', '${targetPath}${file}', library, include, exclude, embed); - } - } - else - { - if (this.filter(file, include, exclude)) - { - addAsset('${path}/${file}', '${targetPath}${file}', library, embed); - } - } - } - } - - /** - * Add an asset library to the game build. - * - * @param name The name of the library. - * @param embed - * @param preload - */ - public function addAssetLibrary(name:String, embed:Bool = false, preload:Bool = false):Void - { - // sourcePath, name, type, embed, preload, generate, prefix - var sourcePath = ''; - this.libraries.push(new Library(sourcePath, name, null, embed, preload, false, '')); - } - - // - // PROCESS FUNCTIONS - // - - /** - * A CLI command to run a command in the shell. - */ - public function buildCLICommand(cmd:String):CLICommand - { - return CommandHelper.fromSingleString(cmd); - } - - /** - * A CLI command to run a Haxe script via `--interp`. - */ - public function buildHaxeCLICommand(path:String):CLICommand - { - return CommandHelper.interpretHaxe(path); - } - - public function getGitCommit():String - { - // Cannibalized from GitCommit.hx - var process = new sys.io.Process('git', ['rev-parse', 'HEAD']); - if (process.exitCode() != 0) - { - var message = process.stderr.readAll().toString(); - error('[ERROR] Could not determine current git commit; is this a proper Git repository?'); - } - - var commitHash:String = process.stdout.readLine(); - var commitHashSplice:String = commitHash.substr(0, 7); - - process.close(); - - return commitHashSplice; - } - - public function getGitBranch():String - { - // Cannibalized from GitCommit.hx - var branchProcess = new sys.io.Process('git', ['rev-parse', '--abbrev-ref', 'HEAD']); - - if (branchProcess.exitCode() != 0) - { - var message = branchProcess.stderr.readAll().toString(); - error('Could not determine current git branch; is this a proper Git repository?'); - } - - var branchName:String = branchProcess.stdout.readLine(); - - branchProcess.close(); - - return branchName; - } - - public function getGitModified():Bool - { - var branchProcess = new sys.io.Process('git', ['status', '--porcelain']); - - if (branchProcess.exitCode() != 0) - { - var message = branchProcess.stderr.readAll().toString(); - error('Could not determine current git status; is this a proper Git repository?'); - } - - var output:String = ''; - try - { - output = branchProcess.stdout.readLine(); - } - catch (e) - { - if (e.message == 'Eof') - { - // Do nothing - // Eof = No output - } - else - { - // Rethrow other exceptions - throw e; - } - } - - branchProcess.close(); - - return output.length > 0; - } - - // - // LOGGING FUNCTIONS - // ============================== - - /** - * Display an info message. This should not interfere with the build process. - */ - public function info(message:String):Void - { - if (command != 'display') - { - Log.info('[INFO] ${message}'); - } - } - - /** - * Display an error message. This should stop the build process. - */ - public function error(message:String):Void - { - Log.error('${message}'); - } + // + // METADATA + // =========================== + + /** + * The game's version number, as a Semantic Versioning string with no prefix. + * REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES! + * You only have to change it here, the rest of the game will query this value. + */ + static final VERSION:String = '0.1.0-demo'; + + /** + * The game's name. Used as the default window title. + */ + static final TITLE:String = 'Starcore'; + + /** + * The name of the generated executable file. + * For example, `STARCORE` will create a file called `STARCORE.exe`. + */ + static final EXECUTABLE_NAME:String = 'STARCORE'; + + /** + * The relative location of the source code. + */ + static final SOURCE_DIR:String = 'source'; + + /** + * The fully qualified class path for the game's preloader. + * Particularly important on HTML5 but it's used on all platforms. + */ + static final PRELOADER:String = 'flixel.system.FlxPreloader'; + + /** + * A package name used for identifying the app on various app stores. + */ + static final PACKAGE_NAME:String = 'com.bitmapstudios.starcore'; + + /** + * The fully qualified class path for the entry point class to execute when launching the game. + * It's where `public static function main():Void` goes. + */ + static final MAIN_CLASS:String = 'Main'; + + /** + * The company name for the game. + * This appears as metadata in many places. + */ + static final COMPANY:String = 'BitMap Studios'; + + /** + * Path to the Haxe script run before building the game. + */ + // static final PREBUILD_HX:String = 'source/Prebuild.hx'; + /** + * Path to the Haxe script run after building the game. + */ + // static final POSTBUILD_HX:String = 'source/Postbuild.hx'; + + /** + * Asset path globs to always exclude from asset libraries. + */ + static final EXCLUDE_ASSETS:Array = ['.*', 'cvs', 'thumbs.db', 'desktop.ini', '*.hash', '*.md']; + + /** + * Asset path globs to exclude on web platforms. + */ + static final EXCLUDE_ASSETS_WEB:Array = ['*.ogg']; + + /** + * Asset path globs to exclude on native platforms. + */ + static final EXCLUDE_ASSETS_NATIVE:Array = ['*.mp3']; + + // + // FEATURE FLAGS + // ========================================== + + /** + * Allows the game to be displayed in the user's Discord "Activity" box + * (it can still be disabled by the user in-game if the option is off). + */ + static final DISCORD_RPC_ALLOWED:FeatureFlag = 'DISCORD_RPC_ALLOWED'; + + /** + * Enables the game to use advanced shaders, giving it a more unsettling and unnerving vibe. + * Sort of like the analog horror videos you see on YouTube. + */ + static final ADVANCED_SHADERS_ALLOWED:FeatureFlag = 'ADVANCED_SHADERS_ALLOWED'; + + /** + * Allows the developer(s) to use built-in editors, + * such as creating animations for entities. + */ + static final DEBUG_EDITORS_ALLOWED:FeatureFlag = 'DEBUG_EDITORS_ALLOWED'; + + /** + * Permits the game to use a built-in logging system, which writes all + * current logs to a new text file inside of a folder called `logs`, + * located in the games output folder. + */ + static final LOGGING_ALLOWED:FeatureFlag = 'LOGGING_ALLOWED'; + + /** + * Enables the game to programmatically change sounds and add + * all kinds of effects to them. + */ + static final SOUND_FILTERS_ALLOWED:FeatureFlag = 'SOUND_FILTERS_ALLOWED'; + + public function new() + { + super(); + + flair(); + configureApp(); + + displayTarget(); + configureOutputDir(); + configureFeatureFlags(); + configureCompileDefines(); + configureHaxelibs(); + configureAssets(); + configureIcons(); // TODO: Implement code when icons are made! + } + + /** + * Display some fancy info before initializing. + */ + function flair() + { + info('STARCORE'); + info('Setting up build...'); + info('Target Version: ' + VERSION); + info('Git Branch: ' + getGitBranch()); + info('Git Commit: ' + getGitCommit()); + info('Git Modified? ' + getGitModified()); + info('Display? ' + isDisplay()); + logSeparator(); + } + + /** + * Apply basic project metadata, such as the game title and version number, + * as well as info like the package name and company (used by various app stores). + */ + function configureApp() + { + // + // METADATA + // ====================================== + this.meta.title = TITLE; + this.meta.version = VERSION; + this.meta.packageName = PACKAGE_NAME; + this.meta.company = COMPANY; + this.meta.description = 'A space themed, open world survival sandbox game.'; + this.meta.companyId = COMPANY; + this.meta.companyUrl = COMPANY; + + // + // CODE + // ================================= + this.sources.push(SOURCE_DIR); // Source code location (more than one can be added) + + // + // PREBUILD & POSTBUILD + // ================================================== + // this.preBuildCallbacks.push(buildHaxeCLICommand(PREBUILD_HX)); + // this.postBuildCallbacks.push(buildHaxeCLICommand(POSTBUILD_HX)); + + // + // APP + // ========================== + this.app.main = MAIN_CLASS; + this.app.preloader = PRELOADER; + this.app.file = EXECUTABLE_NAME; + // These values are only used by the SWF target + // this.app.path + // this.app.init + // this.app.swfVersion + // this.app.url + + // + // WINDOW + // ===================================== + this.window.fps = 60; + this.window.width = (!isMobile()) ? 960 : 0; + this.window.height = (!isMobile()) ? 720 : 0; + this.window.background = 0xFF000000; + this.window.fullscreen = false; + this.window.resizable = isWeb(); + this.window.orientation = (isDesktop() || isMobile()) ? Orientation.LANDSCAPE : Orientation.AUTO; + this.window.hardware = true; + this.window.vsync = false; + this.window.allowHighDPI = true; // Force / allow high DPI + } + + /** + * Log information about the configured target platform. + */ + function displayTarget() + { + // Display the target operating system + switch (this.target) + { + case Platform.WINDOWS: + info('Target Platform: Windows'); + case Platform.MAC: + info('Target Platform: MacOS'); + case Platform.LINUX: + info('Target Platform: Linux'); + case Platform.ANDROID: + info('Target Platform: Android'); + case Platform.IOS: + info('Target Platform: IOS'); + case Platform.HTML5: + info('Target Platform: HTML5'); + // See lime.tools.Platform for a full list + // case Platform.EMSCRITEN: + // case Platform.AIR: + // case Platform.BLACKBERRY: + // case Platform.CONSOLE_PC: + // case Platform.FIREFOX: + // case Platform.FLASH: + // case Platform.PS3: + // case Platform.PS4: + // case Platform.TIZEN: + // case Platform.TVOS: + // case Platform.VITA: + // case Platform.WEBOS: + // case Platform.WIIU: + // case Platform.XBOX1: + default: + error('Unsupported platform (got ${target})'); + } + + switch (this.platformType) + { + case PlatformType.DESKTOP: + info('Target Platform Type: Desktop'); + case PlatformType.MOBILE: + info('Target Platform Type: Mobile'); + case PlatformType.WEB: + info('Target Platform Type: Web'); + case PlatformType.CONSOLE: + info('Target Platform Type: Console'); + default: + error('Unknown target platform type (got ${platformType})'); + } + + // Print whether we are using HXCPP, HashLink, or something else. + if (isWeb()) + { + info('Target Language: JavaScript (HTML5)'); + } + else if (isHashLink()) + { + info('Target Language: HashLink'); + } + else if (isNeko()) + { + info('Target Language: Neko'); + } + else if (isJava()) + { + info('Target Language: Java'); + } + else if (isNodeJS()) + { + info('Target Language: JavaScript (NodeJS)'); + } + else if (isCSharp()) + { + info('Target Language: C#'); + } + else if (isCPlusPlus()) + { + info('Target Language: C++'); + } + else + { + info('Target Language: Unknown'); + } + + for (arch in this.architectures) + { + // Display the list of target architectures. + switch (arch) + { + case Architecture.X86: + info('Architecture: x86'); + case Architecture.X64: + info('Architecture: x64'); + case Architecture.ARMV5: + info('Architecture: ARMv5'); + case Architecture.ARMV6: + info('Architecture: ARMv6'); + case Architecture.ARMV7: + info('Architecture: ARMv7'); + case Architecture.ARMV7S: + info('Architecture: ARMv7S'); + case Architecture.ARM64: + info('Architecture: ARMx64'); + case Architecture.MIPS: + info('Architecture: MIPS'); + case Architecture.MIPSEL: + info('Architecture: MIPSEL'); + case null: + if (!isWeb()) + { + error('Unsupported architecture (got null on non-web platform)'); + } + else + { + info('Architecture: Web'); + } + default: + error('Unsupported architecture (got ${arch})'); + } + } + } + + /** + * Apply various feature flags based on the target platform and the user-provided build flags. + */ + function configureFeatureFlags() + { + // Enable Discord rich presence + // (works only for Windows). + DISCORD_RPC_ALLOWED.apply(this, isWindows()); + + // Enable dope ass screen filters + // (if the platform is on desktop). + ADVANCED_SHADERS_ALLOWED.apply(this, isDesktop()); + + // Enable the editors if the game is in debug mode, + // otherwise, disable them of course. + DEBUG_EDITORS_ALLOWED.apply(this, isDesktop() && isDebug()); + + // Enables dope ass logging + // (only works on desktop). + LOGGING_ALLOWED.apply(this, isDesktop()); + + // Allows the game to programmatically change sounds + // (only works on desktop). + SOUND_FILTERS_ALLOWED.apply(this, isDesktop()); + + logSeparator(); + } + + /** + * Set compilation flags which are not feature flags. + */ + function configureCompileDefines() + { + // Enable OpenFL's error handler. + // Required for the crash logger. + setHaxedef('openfl-enable-handle-error'); + + // Enable stack trace tracking. Good for debugging but has a (minor) performance impact. + setHaxedef('HXCPP_CHECK_POINTER'); + setHaxedef('HXCPP_STACK_LINE'); + setHaxedef('HXCPP_STACK_TRACE'); + setHaxedef('hscriptPos'); + + setHaxedef('safeMode'); + + // Disable the built in pause screen when unfocusing the game. + setHaxedef('FLX_NO_FOCUS_LOST_SCREEN'); + + // Disable the Flixel debugger entirely, since it + // messes with the filters sometimes. + setHaxedef('FLX_NO_DEBUG'); + + if (isRelease()) + { + // Improve performance on Nape. + setHaxedef('NAPE_RELEASE_BUILD'); + } + + // Cleaner looking compiler errors. + setHaxedef('message.reporting', 'pretty'); + } + + function configureOutputDir() + { + // Set the output directory. + // Depends on the target platform and build type. + var buildDir = 'export/${isDebug() ? 'debug' : isRelease() ? 'release' : 'test'}'; + buildDir += '/'; + info('Output Directory: $buildDir'); + app.path = buildDir; + logSeparator(); + } + + function configureHaxelibs() + { + // + // FLIXEL + // ================== + + // Convert the game to run on other platforms. + addHaxelib('lime'); + // Many frontend and backend utilities for the game's display. + addHaxelib('openfl'); + // Core game library. + addHaxelib('flixel'); + // Additional utilities for Flixel. + addHaxelib('flixel-addons'); + // VSCode debug support. + if (isDebug()) + { + addHaxelib('hxcpp-debug-server'); + } + + // + // CUSTOM + // =================== + + // Manipulate sound effects programmatically. + if (SOUND_FILTERS_ALLOWED.isEnabled(this)) + { + addHaxelib('flxsoundfilters'); + } + + // Discord rich presence. + if (DISCORD_RPC_ALLOWED.isEnabled(this)) + { + addHaxelib('hxdiscord_rpc'); + } + } + + function configureAssets() + { + // Add font assets. + addAssetPath('assets/fonts', 'assets/fonts', 'default', ['*.ttf'], []); + + // Add entity assets. + addAssetPath('assets/entities/data', 'assets/entities/data', 'default', ['*.json'], []); + addAssetPath('assets/entities/textures', 'assets/entities/textures', 'default', ['*.png', '*.xml'], []); + + // Add tile assets. + addAssetPath('assets/tiles/data', 'assets/tiles/data', 'default', ['*.json'], []); + addAssetPath('assets/tiles/textures', 'assets/tiles/textures', 'default', ['*.png'], []); + + // Add shared image assets. + addAssetPath('assets/shared/images', 'assets/shared/images', 'default', ['*.png'], []); + + // Add shader frag assets. + addAssetPath('assets/shaders', 'assets/shaders', 'default', ['*.frag'], []); + + if (isWeb()) + { + // Add shared music and sound assets for web (.mp3). + addAssetPath('assets/shared/music', 'assets/shared/music', 'default', ['*.mp3'], ['*.ogg']); + addAssetPath('assets/shared/sounds', 'assets/shared/sounds', 'default', ['*.mp3'], ['*.ogg']); + } + else if (isDesktop()) + { + // Add shared music and sound assets for desktop (.ogg). + addAssetPath('assets/shared/music', 'assets/shared/music', 'default', ['*.ogg'], ['*.mp3']); + addAssetPath('assets/shared/sounds', 'assets/shared/sounds', 'default', ['*.ogg'], ['*.mp3']); + } + } + + /** + * Configure the application's favicon and executable icon. + */ + function configureIcons() {} + + // + // HELPER FUNCTIONS + // (Easy functions to make the code more readable.) + // ======================================================================== + + public function isWeb():Bool + { + return this.platformType == PlatformType.WEB; + } + + public function isMobile():Bool + { + return this.platformType == PlatformType.MOBILE; + } + + public function isDesktop():Bool + { + return this.platformType == PlatformType.DESKTOP; + } + + public function isConsole():Bool + { + return this.platformType == PlatformType.CONSOLE; + } + + public function is32Bit():Bool + { + return this.architectures.contains(Architecture.X86); + } + + public function is64Bit():Bool + { + return this.architectures.contains(Architecture.X64); + } + + public function isWindows():Bool + { + return this.target == Platform.WINDOWS; + } + + public function isMac():Bool + { + return this.target == Platform.MAC; + } + + public function isLinux():Bool + { + return this.target == Platform.LINUX; + } + + public function isAndroid():Bool + { + return this.target == Platform.ANDROID; + } + + public function isIOS():Bool + { + return this.target == Platform.IOS; + } + + public function isHashLink():Bool + { + return this.targetFlags.exists('hl'); + } + + public function isNeko():Bool + { + return this.targetFlags.exists('neko'); + } + + public function isJava():Bool + { + return this.targetFlags.exists('java'); + } + + public function isNodeJS():Bool + { + return this.targetFlags.exists('nodejs'); + } + + public function isCSharp():Bool + { + return this.targetFlags.exists('cs'); + } + + public function isCPlusPlus():Bool + { + return this.defines.exists('cpp'); // Why in defines and not target flags... + } + + public function isDisplay():Bool + { + return this.command == 'display'; + } + + public function isDebug():Bool + { + return this.debug; + } + + public function isRelease():Bool + { + return this.defines.exists('final'); + } + + public function getHaxedef(name:String):Null + { + return this.haxedefs.get(name); + } + + public function setHaxedef(name:String, ?value:String):Void + { + if (value == null) + value = ''; + + this.haxedefs.set(name, value); + } + + public function unsetHaxedef(name:String):Void + { + this.haxedefs.remove(name); + } + + public function getDefine(name:String):Null + { + return this.defines.get(name); + } + + public function hasDefine(name:String):Bool + { + return this.defines.exists(name); + } + + /** + * Add a library to the list of dependencies for the project. + * + * @param name The name of the library to add. + * @param version The version of the library to add. Optional. + */ + public function addHaxelib(name:String, version:String = ''):Void + { + this.haxelibs.push(new Haxelib(name, version)); + } + + /** + * Add a `haxeflag` to the project. + */ + public function addHaxeFlag(value:String):Void + { + this.haxeflags.push(value); + } + + /** + * Call a Haxe build macro. + */ + public function addHaxeMacro(value:String):Void + { + addHaxeFlag('--macro ${value}'); + } + + /** + * Add an icon to the project. + * + * @param icon The path to the icon. + * @param size The size of the icon. Optional. + */ + public function addIcon(icon:String, ?size:Int):Void + { + this.icons.push(new Icon(icon, size)); + } + + /** + * Add an asset to the game build. + * + * @param path The path the asset is located at. + * @param rename The path the asset should be placed. + * @param library The asset library to add the asset to. `null` = 'default' + * @param embed Whether to embed the asset in the executable. + */ + public function addAsset(path:String, ?rename:String, ?library:String, embed:Bool = false):Void + { + // path, rename, type, embed, setDefaults + var asset = new Asset(path, rename, null, embed, true); + @:nullSafety(Off) + { + asset.library = library ?? 'default'; + } + this.assets.push(asset); + } + + /** + * Add an entire path of assets to the game build. + * + * @param path The path the assets are located at. + * @param rename The path the assets should be placed. + * @param library The asset library to add the assets to. `null` = 'default' + * @param include An optional array to include specific asset names. + * @param exclude An optional array to exclude specific asset names. + * @param embed Whether to embed the assets in the executable. + */ + public function addAssetPath(path:String, ?rename:String, library:String, ?include:Array, ?exclude:Array, embed:Bool = false):Void + { + // Argument parsing. + if (path == '') + return; + + if (include == null) + include = []; + + if (exclude == null) + exclude = []; + + var targetPath = rename ?? path; + if (targetPath != '') + targetPath += '/'; + + // Validate path + if (!sys.FileSystem.exists(path)) + { + error('Could not find asset path "${path}".'); + } + else if (!sys.FileSystem.isDirectory(path)) + { + error('Could not parse asset path "${path}", expected a directory.'); + } + else + { + info('Adding asset path ${path}...'); + } + + for (file in sys.FileSystem.readDirectory(path)) + { + if (sys.FileSystem.isDirectory('${path}/${file}')) + { + // Attempt to recursively add all assets in the directory + if (this.filter(file, ['*'], exclude)) + { + addAssetPath('${path}/${file}', '${targetPath}${file}', library, include, exclude, embed); + } + } + else + { + if (this.filter(file, include, exclude)) + { + addAsset('${path}/${file}', '${targetPath}${file}', library, embed); + } + } + } + } + + /** + * Add an asset library to the game build. + * + * @param name The name of the library. + * @param embed + * @param preload + */ + public function addAssetLibrary(name:String, embed:Bool = false, preload:Bool = false):Void + { + // sourcePath, name, type, embed, preload, generate, prefix + var sourcePath = ''; + this.libraries.push(new Library(sourcePath, name, null, embed, preload, false, '')); + } + + // + // PROCESS FUNCTIONS + // + + /** + * A CLI command to run a command in the shell. + */ + public function buildCLICommand(cmd:String):CLICommand + { + return CommandHelper.fromSingleString(cmd); + } + + /** + * A CLI command to run a Haxe script via `--interp`. + */ + public function buildHaxeCLICommand(path:String):CLICommand + { + return CommandHelper.interpretHaxe(path); + } + + public function getGitCommit():String + { + // Cannibalized from GitCommit.hx + var process = new sys.io.Process('git', ['rev-parse', 'HEAD']); + if (process.exitCode() != 0) + { + var message = process.stderr.readAll().toString(); + error('[ERROR] Could not determine current git commit; is this a proper Git repository?'); + } + + var commitHash:String = process.stdout.readLine(); + var commitHashSplice:String = commitHash.substr(0, 7); + + process.close(); + + return commitHashSplice; + } + + public function getGitBranch():String + { + // Cannibalized from GitCommit.hx + var branchProcess = new sys.io.Process('git', ['rev-parse', '--abbrev-ref', 'HEAD']); + + if (branchProcess.exitCode() != 0) + { + var message = branchProcess.stderr.readAll().toString(); + error('Could not determine current git branch; is this a proper Git repository?'); + } + + var branchName:String = branchProcess.stdout.readLine(); + + branchProcess.close(); + + return branchName; + } + + public function getGitModified():Bool + { + var branchProcess = new sys.io.Process('git', ['status', '--porcelain']); + + if (branchProcess.exitCode() != 0) + { + var message = branchProcess.stderr.readAll().toString(); + error('Could not determine current git status; is this a proper Git repository?'); + } + + var output:String = ''; + try + { + output = branchProcess.stdout.readLine(); + } + catch (e) + { + if (e.message == 'Eof') + { + // Do nothing + // Eof = No output + } + else + { + // Rethrow other exceptions + throw e; + } + } + + branchProcess.close(); + + return output.length > 0; + } + + // + // LOGGING FUNCTIONS + // ============================== + + /** + * Display an info message. This should not interfere with the build process. + */ + public function info(message:String):Void + { + if (command != 'display') + { + Log.info('[INFO] ${message}'); + } + } + + /** + * Display an error message. This should stop the build process. + */ + public function error(message:String):Void + { + Log.error('${message}'); + } /** * Display a very long line to separate sections of the game's @@ -877,115 +877,115 @@ class Project extends HXProject */ abstract FeatureFlag(String) { - static final INVERSE_PREFIX:String = 'NO_'; - - public function new(input:String) - { - this = input; - } - - @:from - public static function fromString(input:String):FeatureFlag - { - return new FeatureFlag(input); - } - - /** - * Enable/disable a feature flag if it is unset, and handle the inverse flag. - * Doesn't override a feature flag that was set explicitly. - * @param enableByDefault Whether to enable this feature flag if it is unset. - */ - public function apply(project:Project, enableByDefault:Bool = false):Void - { - // TODO: Name this function better? - - if (isEnabled(project)) - { - // If this flag was already enabled, disable the inverse - project.info('Enabling feature flag ${this}'); - getInverse().disable(project, false); - } - else if (getInverse().isEnabled(project)) - { - // If the inverse flag was already enabled, disable this flag - project.info('Disabling feature flag ${this}'); - disable(project, false); - } - else - { - if (enableByDefault) - { - // Enable this flag if it was unset, and disable the inverse - project.info('Enabling feature flag ${this}'); - enable(project, true); - } - else - { - // Disable this flag if it was unset, and enable the inverse - project.info('Disabling feature flag ${this}'); - disable(project, true); - } - } - } - - /** - * Enable this feature flag by setting the appropriate compile define. - * - * @param project The project to modify. - * @param andInverse Also disable the feature flag's inverse. - */ - public function enable(project:Project, andInverse:Bool = true) - { - project.setHaxedef(this, ''); - if (andInverse) - { - getInverse().disable(project, false); - } - } - - /** - * Disable this feature flag by removing the appropriate compile define. - * - * @param project The project to modify. - * @param andInverse Also enable the feature flag's inverse. - */ - public function disable(project:Project, andInverse:Bool = true) - { - project.unsetHaxedef(this); - if (andInverse) - { - getInverse().enable(project, false); - } - } - - /** - * Query if this feature flag is enabled. - * @param project The project to query. - */ - public function isEnabled(project:Project):Bool - { - // Check both Haxedefs and Defines for this flag - return project.haxedefs.exists(this) || project.defines.exists(this); - } - - /** - * Query if this feature flag's inverse is enabled. - */ - public function isDisabled(project:Project):Bool - { - return getInverse().isEnabled(project); - } - - /** - * Return the inverse of this feature flag. - * @return A new feature flag that is the inverse of this one. - */ - public function getInverse():FeatureFlag - { - if (this.startsWith(INVERSE_PREFIX)) - { - return this.substring(INVERSE_PREFIX.length); - } - return INVERSE_PREFIX + this; - } + static final INVERSE_PREFIX:String = 'NO_'; + + public function new(input:String) + { + this = input; + } + + @:from + public static function fromString(input:String):FeatureFlag + { + return new FeatureFlag(input); + } + + /** + * Enable/disable a feature flag if it is unset, and handle the inverse flag. + * Doesn't override a feature flag that was set explicitly. + * @param enableByDefault Whether to enable this feature flag if it is unset. + */ + public function apply(project:Project, enableByDefault:Bool = false):Void + { + // TODO: Name this function better? + + if (isEnabled(project)) + { + // If this flag was already enabled, disable the inverse + project.info('Enabling feature flag ${this}'); + getInverse().disable(project, false); + } + else if (getInverse().isEnabled(project)) + { + // If the inverse flag was already enabled, disable this flag + project.info('Disabling feature flag ${this}'); + disable(project, false); + } + else + { + if (enableByDefault) + { + // Enable this flag if it was unset, and disable the inverse + project.info('Enabling feature flag ${this}'); + enable(project, true); + } + else + { + // Disable this flag if it was unset, and enable the inverse + project.info('Disabling feature flag ${this}'); + disable(project, true); + } + } + } + + /** + * Enable this feature flag by setting the appropriate compile define. + * + * @param project The project to modify. + * @param andInverse Also disable the feature flag's inverse. + */ + public function enable(project:Project, andInverse:Bool = true) + { + project.setHaxedef(this, ''); + if (andInverse) + { + getInverse().disable(project, false); + } + } + + /** + * Disable this feature flag by removing the appropriate compile define. + * + * @param project The project to modify. + * @param andInverse Also enable the feature flag's inverse. + */ + public function disable(project:Project, andInverse:Bool = true) + { + project.unsetHaxedef(this); + if (andInverse) + { + getInverse().enable(project, false); + } + } + + /** + * Query if this feature flag is enabled. + * @param project The project to query. + */ + public function isEnabled(project:Project):Bool + { + // Check both Haxedefs and Defines for this flag + return project.haxedefs.exists(this) || project.defines.exists(this); + } + + /** + * Query if this feature flag's inverse is enabled. + */ + public function isDisabled(project:Project):Bool + { + return getInverse().isEnabled(project); + } + + /** + * Return the inverse of this feature flag. + * @return A new feature flag that is the inverse of this one. + */ + public function getInverse():FeatureFlag + { + if (this.startsWith(INVERSE_PREFIX)) + { + return this.substring(INVERSE_PREFIX.length); + } + return INVERSE_PREFIX + this; + } } diff --git a/setup/install-libs-macos-linux.sh b/setup/install-libs-macos-linux.sh new file mode 100644 index 0000000..2dc9f8d --- /dev/null +++ b/setup/install-libs-macos-linux.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Shell script to install all Haxe libraries from hmm.json +# Make sure you have Haxe and Haxelib installed and in your PATH + +# Install haxelib dependencies +haxelib install lime 8.2.2 +haxelib install openfl 9.4.1 +haxelib install flixel 6.1.0 +haxelib install flixel-addons 3.3.2 +haxelib install flixel-tools 1.5.1 +haxelib install hxcpp-debug-server 1.2.4 +haxelib install hxdiscord_rpc 1.3.0 +haxelib install hxcpp 4.3.2 +haxelib install hxp 1.3.0 + +# Install git dependencies +haxelib git flxsoundfilters https://github.com/TheZoroForce240/FlxSoundFilters + +echo "All libraries installed!" diff --git a/setup/install-libs-windows.bat b/setup/install-libs-windows.bat new file mode 100644 index 0000000..2422d7a --- /dev/null +++ b/setup/install-libs-windows.bat @@ -0,0 +1,20 @@ +@echo off +REM Batch script to install all Haxe libraries from hmm.json +REM Make sure you have Haxe and Haxelib installed and in your PATH + +REM Install haxelib dependencies +haxelib install lime 8.2.2 +haxelib install openfl 9.4.1 +haxelib install flixel 6.1.0 +haxelib install flixel-addons 3.3.2 +haxelib install flixel-tools 1.5.1 +haxelib install hxcpp-debug-server 1.2.4 +haxelib install hxdiscord_rpc 1.3.0 +haxelib install hxcpp 4.3.2 +haxelib install hxp 1.3.0 + +REM Install git dependencies +haxelib git flxsoundfilters https://github.com/TheZoroForce240/FlxSoundFilters + +echo All libraries installed! +pause diff --git a/source/InitState.hx b/source/InitState.hx index c5d0cb5..5df8f16 100644 --- a/source/InitState.hx +++ b/source/InitState.hx @@ -33,10 +33,9 @@ class InitState extends FlxState { override public function create():Void { - // Setup the logger for Starcore + // Setup the logger for Starcore. LoggerUtil.initialize(); - // Log that we are setting up Starcore LoggerUtil.log('INITIALIZING STARCORE SETUP', INFO, false); // Load all of the player's settings and options. @@ -66,7 +65,6 @@ class InitState extends FlxState function configureFlixelSettings():Void { - // Log info LoggerUtil.log('Configuring Flixel settings'); // Set the cursor to be the system default, rather than using a custom cursor. diff --git a/source/starcore/backend/util/FlixelUtil.hx b/source/starcore/backend/util/FlixelUtil.hx index e81a108..cccd1bc 100644 --- a/source/starcore/backend/util/FlixelUtil.hx +++ b/source/starcore/backend/util/FlixelUtil.hx @@ -6,15 +6,18 @@ import flixel.FlxState; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.input.keyboard.FlxKey; import flixel.sound.FlxSound; +import flixel.system.FlxAssets.FlxShader; import flixel.tweens.FlxTween; import flixel.util.FlxColor; import flixel.util.FlxTimer; import haxe.Exception; +import openfl.filters.BitmapFilter; import openfl.filters.ShaderFilter; import starcore.backend.api.DiscordClient; import starcore.backend.data.ClientPrefs.ShaderModeType; import starcore.backend.data.Constants; import starcore.shaders.*; +import starcore.shaders.bases.UpdatedShader; #if SOUND_FILTERS_ALLOWED import flixel.sound.filters.FlxFilteredSound; import flixel.sound.filters.FlxSoundFilter; @@ -49,6 +52,27 @@ final class FlixelUtil { function new() {} + static var currentShadersApplied:Array = []; + + /** + * Configures the Flixel utility class. + * + * This should only be called once, when the game first starts up. + */ + public static function configure():Void + { + FlxG.signals.postUpdate.add(() -> + { + for (shader in currentShadersApplied) + { + if (shader != null && Std.isOfType(shader, UpdatedShader)) + { + (cast shader : UpdatedShader).update(FlxG.elapsed); + } + } + }); + } + /** * Fades into a state with a cool transition effect. * @@ -135,32 +159,45 @@ final class FlixelUtil */ public static function setFilters(?mode:ShaderModeType):Void { + // Completely reset the filters. + currentShadersApplied.splice(0, currentShadersApplied.length); + var toAdd:Array = []; + switch (mode) { #if ADVANCED_SHADERS_ALLOWED case DEFAULT | null: - FlxG.game.setFilters([ - new ShaderFilter(CacheUtil.vcrBorderShader), - new ShaderFilter(CacheUtil.vcrMario85Shader), - new ShaderFilter(CacheUtil.grainShader), - new ShaderFilter(new Hq2xShader()), - new ShaderFilter(new TiltshiftShader()) - ]); + currentShadersApplied = [ + new Hq2xShader(), + new TiltshiftShader(), + new NTSCShader(), + new SkewShader(), + new VCRLinesShader(), + new GrainShader() + ]; #end case FAST: + currentShadersApplied = [new GrainShader(), new Hq2xShader(), new TiltshiftShader(), new ScanlineShader()]; + case MINIMAL: FlxG.game.setFilters([ - new ShaderFilter(CacheUtil.grainShader), - new ShaderFilter(new ScanlineShader()), - new ShaderFilter(new Hq2xShader()), - new ShaderFilter(new TiltshiftShader()) + new ShaderFilter(CacheUtil.grainShader), + new ShaderFilter(new Hq2xShader()) ]); - case MINIMAL: - FlxG.game.setFilters([new ShaderFilter(CacheUtil.grainShader), new ShaderFilter(new Hq2xShader())]); case NONE: - FlxG.game.setFilters([]); + currentShadersApplied = []; default: - FlxG.game.setFilters([]); + currentShadersApplied = []; } + // Apply all the shaders as ShaderFilters. + for (shader in currentShadersApplied) + { + if (shader != null) + { + toAdd.push(new ShaderFilter(shader)); + } + } + + FlxG.game.setFilters(toAdd); } /** @@ -253,7 +290,7 @@ final class FlixelUtil * ### *NOTE: If `FlxKey.PLUS` is passed down, it will be returned as `=` if `shiftVariant` is false!* * * @param key The key to be converted. - * @param shiftVariant If the key should be converted to the variant when the + * @param shiftVariant If the key should be converted to its shift variant when the * user is pressing shift (i.e. `a` would become `A`, `;` would * become `:`, etc.). Default value is `false`. * @return The converted character. @@ -477,15 +514,11 @@ final class FlixelUtil */ public static function closeGame(sysShutdown:Bool = true):Void { - // Log info LoggerUtil.log('SHUTTING DOWN STARCORE', INFO, false); - // Save all of the user's data SaveUtil.saveAll(); - // Shutdown Discord rich presence DiscordClient.shutdown(); - // Shutdown the logging system LoggerUtil.shutdown(); - // Close the game respectfully + if (sysShutdown) { #if web diff --git a/source/starcore/shaders/GrainShader.hx b/source/starcore/shaders/GrainShader.hx index 488e4c1..f35f98d 100644 --- a/source/starcore/shaders/GrainShader.hx +++ b/source/starcore/shaders/GrainShader.hx @@ -1,15 +1,15 @@ package starcore.shaders; -import openfl.Lib; -import flixel.addons.display.FlxRuntimeShader; import openfl.Assets; +import openfl.Lib; import starcore.backend.util.PathUtil; +import starcore.shaders.bases.UpdatedShader; /** * Adds a grainy effect to the screen like an old TV * (hence the name). */ -class GrainShader extends FlxRuntimeShader +class GrainShader extends UpdatedShader { public function new() { diff --git a/source/starcore/shaders/GrayscaleShader.hx b/source/starcore/shaders/GrayscaleShader.hx index ea172ca..e6514d5 100644 --- a/source/starcore/shaders/GrayscaleShader.hx +++ b/source/starcore/shaders/GrayscaleShader.hx @@ -9,17 +9,14 @@ import starcore.backend.util.PathUtil; */ class GrayscaleShader extends FlxRuntimeShader { - public var amount:Float = 1; - public function new(amount:Float = 1) { super(Assets.getText(PathUtil.ofFrag('grayscale'))); - setAmount(amount); + setFloat("_amount", amount); } public function setAmount(value:Float):Void { - amount = value; - setFloat("_amount", amount); + setFloat("_amount", value); } } diff --git a/source/starcore/shaders/HueShiftShader.hx b/source/starcore/shaders/HueShiftShader.hx index f1f0082..348493a 100644 --- a/source/starcore/shaders/HueShiftShader.hx +++ b/source/starcore/shaders/HueShiftShader.hx @@ -1,8 +1,8 @@ package starcore.filters; -import starcore.backend.util.PathUtil; -import openfl.Assets; import flixel.addons.display.FlxRuntimeShader; +import openfl.Assets; +import starcore.backend.util.PathUtil; /** * Allows a sprite to shift its colors diff --git a/source/starcore/shaders/NTSCShader.hx b/source/starcore/shaders/NTSCShader.hx new file mode 100644 index 0000000..4fce353 --- /dev/null +++ b/source/starcore/shaders/NTSCShader.hx @@ -0,0 +1,13 @@ +package starcore.shaders; + +import flixel.addons.display.FlxRuntimeShader; +import openfl.Assets; +import starcore.backend.util.PathUtil; + +class NTSCShader extends FlxRuntimeShader +{ + public function new() + { + super(Assets.getText(PathUtil.ofFrag('ntsc'))); + } +} diff --git a/source/starcore/shaders/ScanlineShader.hx b/source/starcore/shaders/ScanlineShader.hx index b649ad3..0e85a86 100644 --- a/source/starcore/shaders/ScanlineShader.hx +++ b/source/starcore/shaders/ScanlineShader.hx @@ -1,8 +1,8 @@ package starcore.shaders; -import starcore.backend.util.PathUtil; -import openfl.Assets; import flixel.addons.display.FlxRuntimeShader; +import openfl.Assets; +import starcore.backend.util.PathUtil; /** * Adds old TV lines across the screen. diff --git a/source/starcore/shaders/SkewShader.hx b/source/starcore/shaders/SkewShader.hx new file mode 100644 index 0000000..812ffd8 --- /dev/null +++ b/source/starcore/shaders/SkewShader.hx @@ -0,0 +1,13 @@ +package starcore.shaders; + +import flixel.addons.display.FlxRuntimeShader; +import openfl.Assets; +import starcore.backend.util.PathUtil; + +class SkewShader extends FlxRuntimeShader +{ + public function new() + { + super(Assets.getText(PathUtil.ofFrag('skew'))); + } +} diff --git a/source/starcore/shaders/TiltshiftShader.hx b/source/starcore/shaders/TiltshiftShader.hx index bcec1d0..8c1e1fa 100644 --- a/source/starcore/shaders/TiltshiftShader.hx +++ b/source/starcore/shaders/TiltshiftShader.hx @@ -1,8 +1,8 @@ package starcore.shaders; -import starcore.backend.util.PathUtil; -import openfl.utils.Assets; import flixel.addons.display.FlxRuntimeShader; +import openfl.utils.Assets; +import starcore.backend.util.PathUtil; /** * Adds a slight blur to the top and bottom edges of the screen. diff --git a/source/starcore/shaders/VCRLinesShader.hx b/source/starcore/shaders/VCRLinesShader.hx new file mode 100644 index 0000000..d034103 --- /dev/null +++ b/source/starcore/shaders/VCRLinesShader.hx @@ -0,0 +1,19 @@ +package starcore.shaders; + +import flixel.FlxG; +import openfl.Assets; +import starcore.backend.util.PathUtil; +import starcore.shaders.bases.UpdatedShader; + +class VCRLinesShader extends UpdatedShader +{ + public function new() + { + super(Assets.getText(PathUtil.ofFrag('vcrlines'))); + } + + public function update(elapsed:Float):Void + { + setFloat('time', FlxG.game.ticks * FlxG.elapsed); + } +} diff --git a/source/starcore/shaders/VCRMario85Shader.hx b/source/starcore/shaders/VCRMario85Shader.hx index f45a77e..7698316 100644 --- a/source/starcore/shaders/VCRMario85Shader.hx +++ b/source/starcore/shaders/VCRMario85Shader.hx @@ -1,8 +1,8 @@ package starcore.shaders; -import flixel.addons.display.FlxRuntimeShader; import openfl.Assets; import starcore.backend.util.PathUtil; +import starcore.shaders.bases.UpdatedShader; // https://www.shadertoy.com/view/ldjGzV // https://www.shadertoy.com/view/Ms23DR @@ -12,7 +12,7 @@ import starcore.backend.util.PathUtil; /** * Gives the screen a creepy, old Super Mario-like vibe. */ -class VCRMario85Shader extends FlxRuntimeShader +class VCRMario85Shader extends UpdatedShader { public function new() { diff --git a/source/starcore/shaders/bases/UpdatedShader.hx b/source/starcore/shaders/bases/UpdatedShader.hx new file mode 100644 index 0000000..b25cb90 --- /dev/null +++ b/source/starcore/shaders/bases/UpdatedShader.hx @@ -0,0 +1,21 @@ +package starcore.shaders.bases; + +import flixel.addons.display.FlxRuntimeShader; + +/** + * Base class for shaders that need to be updated every frame. + */ +abstract class UpdatedShader extends FlxRuntimeShader +{ + public function new(?fragCode:String) + { + super(fragCode); + } + + /** + * Function which is called every frame to update the shader. + * + * @param elapsed The amount of time (in seconds) since the last frame. + */ + public abstract function update(elapsed:Float):Void; +}