diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be46446 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_STORE +RECOVER_*.fla + +.haxelib/ +.vs/ +.vscode/ + +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json + +export/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..89e20ed --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "openfl.lime-vscode-extension", + "redhat.vscode-xml" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5e9a7a1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Build + Debug", + "type": "lime", + "request": "launch" + }, + { + "name": "Debug", + "type": "lime", + "request": "launch", + "preLaunchTask": null + }, + { + "name": "Macro", + "type": "haxe-eval", + "request": "launch" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3279906 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "search.exclude": { + "export/**/*.*": true + }, + "[haxe]": { + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.codeActionsOnSave": { + "source.sortImports": "explicit" + } + }, + "haxe.enableExtendedIndentation": true +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..16a7764 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,13 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "lime", + "command": "test", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/Project.hxp b/Project.hxp new file mode 100644 index 0000000..a63e89a --- /dev/null +++ b/Project.hxp @@ -0,0 +1,132 @@ +package; + +import hxp.*; +import lime.tools.*; + +using StringTools; + +/** + * This is the main project file for TechNotDrip Engine. + * + * This works exactly like an Project.xml but in Haxe Syntax. + */ +class Project extends HXProject +{ + public function new() + { + super(); + + setupApplicationSettings(); + setupWindowSettings(); + setupPathSettings(); + setupHaxelibs(); + setupHaxeDefines(); + setupHaxeFlags(); + } + + public function setupApplicationSettings():Void + { + meta.title = 'Friday Night Funkin\': TechNotDrip Engine'; + app.file = 'TechNotDrip'; + app.main = 'Main'; + meta.version = '0.1.0'; + meta.company = 'TilNotDrip'; + app.preloader = 'flixel.system.FlxPreloader'; + app.swfVersion = 11.8; + } + + public function setupWindowSettings():Void + { + window.width = 1280; + window.height = 720; + window.background = 0; + window.hardware = true; + window.vsync = false; + + if (platformType == WEB) + { + window.resizable = true; + } + + if (platformType == DESKTOP) + { + window.orientation = LANDSCAPE; + window.fullscreen = false; + window.resizable = true; + } + + if (platformType == MOBILE) + { + window.orientation = LANDSCAPE; + window.fullscreen = false; + window.width = 0; + window.height = 0; + } + } + + public function setupPathSettings():Void + { + app.path = getBuildDirectory(); + + var excludeList:Array = []; + excludeList.push((platformType == WEB) ? 'ogg' : 'mp3'); + + // TODO: Maybe scan the entire assets folder instead like base fnf. + includeAssets("assets", "assets", ['*'], excludeList); + + icons.push(new Icon('extras/appicons/icon16.png', 16)); + icons.push(new Icon('extras/appicons/icon32.png', 32)); + icons.push(new Icon('extras/appicons/icon64.png', 64)); + icons.push(new Icon('extras/appicons/icon.png')); + + sources.push('src'); + } + + public function setupHaxelibs():Void + { + haxelibs.push(new Haxelib('lime', '8.2.1')); + haxelibs.push(new Haxelib('openfl', '9.4.0')); + haxelibs.push(new Haxelib('flixel', '5.8.0')); + haxelibs.push(new Haxelib('flixel-addons', '3.2.3')); + + if (debug) + haxelibs.push(new Haxelib('hxcpp-debug-server', '1.2.4')); + } + + public function setupHaxeDefines():Void + { + if (!debug) + haxedefs.set('FLX_NO_DEBUG', ''); + + haxedefs.set('FLX_NO_HEALTH', ''); + + if (platformType == MOBILE) + { + haxedefs.set('FLX_NO_KEYBOARD', ''); + haxedefs.set('FLX_NO_MOUSE', ''); + } + + if (platformType == DESKTOP) + { + haxedefs.set('FLX_NO_TOUCH', ''); + } + + haxedefs.set('message.reporting', 'pretty'); + + if (!debug) + haxedefs.set('NAPE_RELEASE_BUILD', ''); + } + + public function setupHaxeFlags():Void + { + haxeflags.push('-dce no'); + haxeflags.push("--macro include('funkin')"); + haxeflags.push("--macro addMetadata('@:build(funkin.macros.ZProperty.build())', 'flixel.FlxBasic')"); + } + + function getBuildDirectory():String + { + var buildDir:String = 'export/' + ((debug) ? 'debug' : 'release'); + return buildDir; + } +} diff --git a/README.md b/README.md deleted file mode 100644 index bb43a96..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# TechNotDrip-Engine -fnf engine diff --git a/checkstyle.json b/checkstyle.json new file mode 100644 index 0000000..0a52a63 --- /dev/null +++ b/checkstyle.json @@ -0,0 +1,139 @@ +{ + "defaultSeverity": "INFO", + "checks": [ + { + "props": {}, + "type": "AvoidStarImport" + }, + { + "props": { + "thresholdSimilar": 100, + "thresholdIdentical": 60 + }, + "type": "CodeSimilarity" + }, + { + "props": {}, + "type": "CommentedOutCode" + }, + { + "props": { + "allowSingleline": true, + "policy": "aligned" + }, + "type": "ConditionalCompilation" + }, + { + "props": { + "format": "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$", + "tokens": ["INLINE"] + }, + "type": "ConstantName" + }, + { + "props": {}, + "type": "DefaultComesLast" + }, + { + "props": { + "option": "empty", + "tokens": [ + "ABSTRACT_DEF", + "CATCH", + "CLASS_DEF", + "ENUM_DEF", + "FOR", + "FUNCTION", + "IF", + "INTERFACE_DEF", + "OBJECT_DECL", + "SWITCH", + "TRY", + "TYPEDEF_DEF", + "WHILE" + ] + }, + "type": "EmptyBlock" + }, + { + "props": { + "enforceEmptyPackage": true + }, + "type": "EmptyPackage" + }, + { + "props": { + "excludeNames": ["new", "toString"], + "fieldType": "BOTH", + "ignoreOverride": true, + "modifier": "PUBLIC", + "requireParams": true, + "requireReturn": true, + "tokens": [ + "ABSTRACT_DEF", + "CLASS_DEF", + "ENUM_DEF", + "INTERFACE_DEF" + ] + }, + "type": "FieldDocComment" + }, + { + "props": { + "modifiers": [ + "OVERRIDE", + "PUBLIC_PRIVATE", + "STATIC", + "EXTERN", + "MACRO", + "INLINE", + "DYNAMIC", + "FINAL" + ] + }, + "type": "ModifierOrder" + }, + { + "props": {}, + "type": "SimplifyBooleanExpression" + }, + { + "props": {}, + "type": "SimplifyBooleanReturn" + }, + { + "props": { + "spaceIfCondition": "should", + "spaceAroundBinop": true, + "spaceForLoop": "should", + "ignoreRangeOperator": true, + "spaceWhileLoop": "should", + "spaceCatch": "should", + "spaceSwitchCase": "should", + "noSpaceAroundUnop": true + }, + "type": "Spacing" + }, + { + "props": {}, + "type": "TrailingWhitespace" + }, + { + "props": {}, + "type": "UnusedLocalVar" + }, + { + "props": { + "typeHintPolicy": "enforce_all" + }, + "type": "VarTypeHint" + }, + { + "props": { + "allowTrailingComma": false, + "tokens": [",", ";"] + }, + "type": "WhitespaceAfter" + } + ] +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..53b7e29 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,2 @@ +# TechNotDrip Engine +fnf engine diff --git a/extras/appicons/icon.png b/extras/appicons/icon.png new file mode 100644 index 0000000..5778aa3 Binary files /dev/null and b/extras/appicons/icon.png differ diff --git a/extras/appicons/icon16.png b/extras/appicons/icon16.png new file mode 100644 index 0000000..ff603f3 Binary files /dev/null and b/extras/appicons/icon16.png differ diff --git a/extras/appicons/icon32.png b/extras/appicons/icon32.png new file mode 100644 index 0000000..f789386 Binary files /dev/null and b/extras/appicons/icon32.png differ diff --git a/extras/appicons/icon64.png b/extras/appicons/icon64.png new file mode 100644 index 0000000..8224264 Binary files /dev/null and b/extras/appicons/icon64.png differ diff --git a/hxformat.json b/hxformat.json new file mode 100644 index 0000000..66cb386 --- /dev/null +++ b/hxformat.json @@ -0,0 +1,15 @@ +{ + "lineEnds": { + "leftCurly": "both", + "rightCurly": "both", + "objectLiteralCurly": { + "leftCurly": "after" + } + }, + "sameLine": { + "ifElse": "next", + "doWhile": "next", + "tryBody": "next", + "tryCatch": "next" + } +} diff --git a/src/Main.hx b/src/Main.hx new file mode 100644 index 0000000..2a3d60c --- /dev/null +++ b/src/Main.hx @@ -0,0 +1,49 @@ +package; + +import flixel.FlxG; +import flixel.FlxGame; +import flixel.util.typeLimit.NextState; +import funkin.states.ui.TitleState; +import openfl.Assets; +import openfl.display.Sprite; + +class Main extends Sprite +{ + var flxGameData:FlxGameInit = { + width: 1280, + height: 720, + initState: TitleState.new, + framerate: 144, + showSplash: false, + startFullscreen: false + }; + + public function new() + { + super(); + + initGame(); + } + + function initGame():Void + { + var flxGame:FlxGame = new FlxGame(flxGameData.width, flxGameData.height, flxGameData.initState, flxGameData.framerate, flxGameData.framerate, + !flxGameData.showSplash, flxGameData.startFullscreen); + addChild(flxGame); + + Assets.cache.enabled = false; + + FlxG.mouse.useSystemCursor = true; + FlxG.mouse.visible = false; + } +} + +typedef FlxGameInit = +{ + var width:Int; + var height:Int; + var initState:InitialState; + var framerate:Int; + var showSplash:Bool; + var startFullscreen:Bool; +} diff --git a/src/funkin/Conductor.hx b/src/funkin/Conductor.hx new file mode 100644 index 0000000..5613e9f --- /dev/null +++ b/src/funkin/Conductor.hx @@ -0,0 +1,149 @@ +package funkin; + +import flixel.util.FlxDestroyUtil.IFlxDestroyable; +import flixel.util.FlxSignal; + +/* + CONDUCTOR TODO: + * Add time changes, so most things arent hardcoded + */ +class Conductor implements IFlxDestroyable +{ + /** + * Signal fired when this instance advances to a new step. + */ + public var stepHit:FlxSignal; + + /** + * Signal fired when this instance advances to a new beat. + */ + public var beatHit:FlxSignal; + + /** + * Signal fired when this instance advances to a new section. + */ + public var sectionHit:FlxSignal; + + /** + * The current step. + */ + public var curStep:Int; + + /** + * The current beat. + */ + public var curBeat:Int; + + /** + * The current section. + */ + public var curSection:Int; + + /** + * Timestamp of the music that the conductor will follow. + * Should be in miliseconds. + */ + public var time(default, set):Float; + + /** + * The bpm of the music that the conductor will follow. + * TODO: tie this to a time change instead. + */ + public var bpm:Float; + + /** + * How many steps in a beat there are. + * TODO: tie this to a time change. + */ + public var beatSteps:Int = 4; + + /** + * How many beats in a section there are. + * TODO: tie this to a time change. + */ + public var sectionBeats:Int = 4; + + /** + * The length between a beat, in miliseconds. + */ + public var crochet(get, null):Float; + + function get_crochet():Float + { + return calculateCrochet(bpm); + } + + /** + * The length between a step, in miliseconds. + */ + public var stepCrochet(get, null):Float; + + function get_stepCrochet():Float + { + return crochet / beatSteps; + } + + /** + * The length between a section, in miliseconds. + */ + public var sectionCrochet(get, null):Float; + + function get_sectionCrochet():Float + { + return crochet * sectionBeats; + } + + public function new() + { + stepHit = new FlxSignal(); + beatHit = new FlxSignal(); + sectionHit = new FlxSignal(); + + bpm = 100; + time = 0; + } + + function set_time(value:Float):Float + { + time = value; + + var oldStep:Int = curStep; + var oldBeat:Int = curBeat; + var oldSection:Int = curSection; + + curStep = Math.floor(time / stepCrochet); + curBeat = Math.floor(time / crochet); + curSection = Math.floor(time / sectionCrochet); + + if (oldStep != curStep) + stepHit.dispatch(); + + if (oldBeat != curBeat) + beatHit.dispatch(); + + if (oldSection != curSection) + sectionHit.dispatch(); + + return value; + } + + /** + * Cleans up this Conductor to the best of our abilities. + */ + public function destroy():Void + { + stepHit.destroy(); + beatHit.destroy(); + sectionHit.destroy(); + } + + /** + * Calculate the crochet, which is the length between a beat. + * @param bpm The bpm to use for calculating. + * @return The crochet, in miliseconds. + */ + public static function calculateCrochet(bpm:Float):Float + { + return ((60 / bpm) * 1000); + } +} diff --git a/src/funkin/import.hx b/src/funkin/import.hx new file mode 100644 index 0000000..c36879c --- /dev/null +++ b/src/funkin/import.hx @@ -0,0 +1,18 @@ +package funkin; + +#if !macro +import flixel.FlxG; +import flixel.FlxSprite; +import flixel.math.FlxMath; +import flixel.sound.FlxSound; +import flixel.text.FlxText; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import flixel.util.FlxTimer; +import funkin.util.paths.Paths; +import haxe.Exception; + +using Lambda; +using StringTools; +#end diff --git a/src/funkin/macros/ZProperty.hx b/src/funkin/macros/ZProperty.hx new file mode 100644 index 0000000..783bf18 --- /dev/null +++ b/src/funkin/macros/ZProperty.hx @@ -0,0 +1,28 @@ +package funkin.macros; + +#if !display +import haxe.macro.Context; +import haxe.macro.Expr; + +class ZProperty +{ + /** + * Builds the field for the `z` property. + * @return New z field. + */ + public static macro function build():Array + { + var fields:Array = Context.getBuildFields(); + + fields.push({ + name: 'z', + doc: 'Z position of this object in world space.', + access: [Access.APublic], + kind: FieldType.FVar(macro :Int, macro $v{0}), + pos: Context.currentPos(), + }); + + return fields; + } +} +#end diff --git a/src/funkin/states/FunkinState.hx b/src/funkin/states/FunkinState.hx new file mode 100644 index 0000000..c9a1fd7 --- /dev/null +++ b/src/funkin/states/FunkinState.hx @@ -0,0 +1,60 @@ +package funkin.states; + +import flixel.FlxBasic; +import flixel.FlxState; +import flixel.util.FlxSort; + +/** + * A FunkinState is a regular FlxState which adds more music functionality for it (etc. making objects play an animation on a beat hit). + */ +class FunkinState extends FlxState +{ + /** + * The conductor that controls everything music-wise inside this state. + */ + public var conductor:Conductor = null; + + public function new() + { + conductor = new Conductor(); + conductor.stepHit.add(stepHit); + conductor.beatHit.add(beatHit); + conductor.sectionHit.add(sectionHit); + + super(); + } + + override public function destroy():Void + { + conductor.destroy(); + conductor = null; + + super.destroy(); + } + + /** + * This function is called after the conductor step changes. + */ + public function stepHit():Void {} + + /** + * This function is called after the conductor beat changes. + */ + public function beatHit():Void {} + + /** + * This function is called after the conductor section changes. + */ + public function sectionHit():Void {} + + /** + * Rearranges all FlxBasic objects by their Z value. + */ + public function rearrange():Void + { + sort((i:Int, basic1:FlxBasic, basic2:FlxBasic) -> + { + return FlxSort.byValues(i, basic1.z, basic2.z); + }, FlxSort.ASCENDING); + } +} diff --git a/src/funkin/states/ui/TitleState.hx b/src/funkin/states/ui/TitleState.hx new file mode 100644 index 0000000..f97cee3 --- /dev/null +++ b/src/funkin/states/ui/TitleState.hx @@ -0,0 +1,14 @@ +package funkin.states.ui; + +class TitleState extends FunkinState +{ + override public function create():Void + { + super.create(); + } + + override public function update(elapsed:Float):Void + { + super.update(elapsed); + } +} diff --git a/src/funkin/util/paths/Paths.hx b/src/funkin/util/paths/Paths.hx new file mode 100644 index 0000000..74aac9f --- /dev/null +++ b/src/funkin/util/paths/Paths.hx @@ -0,0 +1,40 @@ +package funkin.util.paths; + +/** + * I wanted to name this FunkinPath but that would mess up my muscle memory. + */ +class Paths +{ + /** + * The extension used for audio files. + * + * @default ogg (mp3 For web) + */ + public static inline final AUDIO_EXT:String = #if web 'mp3' #else 'ogg' #end; + + /** + * The extenstion used for image files. + * + * @default png + */ + public static inline final IMAGE_EXT:String = 'png'; + + /** + * The extension used for scripting files. + * + * We don't have scripting support yet nor an idea on what haxelib we use for it. + * + * @default hx + */ + public static inline final SCRIPT_EXT:String = 'hx'; + + /** + * The helper for returning content and objects from files. + */ + public static var content:PathsContent = new PathsContent(); + + /** + * The helper for returning strings of locations. + */ + public static var location:PathsLocation = new PathsLocation(); +} diff --git a/src/funkin/util/paths/PathsCache.hx b/src/funkin/util/paths/PathsCache.hx new file mode 100644 index 0000000..9654fe7 --- /dev/null +++ b/src/funkin/util/paths/PathsCache.hx @@ -0,0 +1,254 @@ +package funkin.util.paths; + +import flixel.graphics.FlxGraphic; +import openfl.Assets; +import openfl.display.BitmapData; +import openfl.media.Sound; +import openfl.system.System; + +/** + * A caching system for PathsContent. + * + * This caches content like audios and images for faster returning rather than taking a long time each time. + */ +class PathsCache +{ + /** + * The content that doesn't get wiped from a cache clean. + * + * You should only put something here if you use it on a daily basis. + */ + final removeExcludeKeys:Array = []; + + var cachedAudioKeys:Array; + var cachedImageKeys:Array; + + var cachedAudio:Map; + var cachedBitmapData:Map; + var cachedFlxGraphic:Map; + + /** + * Initializes all variables for use with this class. + */ + public function new() + { + cachedAudioKeys = []; + cachedImageKeys = []; + + cachedAudio = new Map(); + cachedBitmapData = new Map(); + cachedFlxGraphic = new Map(); + } + + /** + * Caches and returns an Sound instance. + * @param key Audio key to cache and return. + * @return Sound Instance from cache. + */ + public function getAudio(key:String):Sound + { + if (cachedAudio.get(key) == null) + { + var audio:Sound = null; + + try + { + audio = Assets.getSound(key, false); + } + catch (e:Exception) + { + trace('[WARNING]: Audio could not be loaded! ($key) More details: ${e.message}'); + return null; + } + + cachedAudio.set(key, audio); + + if (!cachedAudioKeys.contains(key)) + cachedAudioKeys.push(key); + } + + return cachedAudio.get(key); + } + + // TODO: Maybe add gpu caching to images when we get options? + + /** + * Caches and returns an BitmapData instance. + * @param key Image key to cache and return. + * @return BitmapData Instance from cache. + */ + public function getBitmapData(key:String):BitmapData + { + if (cachedBitmapData.get(key) == null) + { + var bitmapData:BitmapData = null; + + try + { + bitmapData = Assets.getBitmapData(key, false); + } + catch (e:Exception) + { + trace('[WARNING]: Image could not be loaded! ($key) More details: ${e.message}'); + return null; + } + + cachedBitmapData.set(key, bitmapData); + + if (!cachedImageKeys.contains(key)) + cachedImageKeys.push(key); + } + + return cachedBitmapData.get(key); + } + + /** + * Caches and returns an FlxGraphic instance. + * @param key Image key to cache and return. + * @return FlxGraphic Instance from cache. + */ + public function getFlxGraphic(key:String):FlxGraphic + { + if (cachedFlxGraphic.get(key) == null) + { + var flxGraphic:FlxGraphic = null; + var bitmapData:BitmapData = null; + + try + { + bitmapData = getBitmapData(key); + } + catch (e:Exception) + { + return null; + } + + flxGraphic = FlxGraphic.fromBitmapData(bitmapData, false, key, false); + flxGraphic.persist = true; + flxGraphic.destroyOnNoUse = false; + + cachedFlxGraphic.set(key, flxGraphic); + + if (!cachedImageKeys.contains(key)) + cachedImageKeys.push(key); + } + + return cachedFlxGraphic.get(key); + } + + /** + * Clears out the cached objects inside of this instance. + * @param runGarbageCollecter Whether to run the garbage collector after clearing all objects. + * @param bypassExcludeKeys Whether to remove the objects even if it's inside of the exclude list. + */ + public function clear(?runGarbageCollecter:Bool = true, ?bypassExcludeKeys:Bool = false):Void + { + clearImages(false, bypassExcludeKeys); + clearAudios(false, bypassExcludeKeys); + + if (runGarbageCollecter) + System.gc(); + } + + /** + * Clears out the cached audios inside of this instance. + * @param runGarbageCollecter Whether to run the garbage collector after clearing all audios. + * @param bypassExcludeKeys Whether to remove the audios even if it's inside of the exclude list. + */ + public function clearAudios(?runGarbageCollecter:Bool = true, ?bypassExcludeKeys:Bool = false):Void + { + for (audio in cachedAudioKeys) + { + removeAudio(audio, bypassExcludeKeys); + } + + if (runGarbageCollecter) + System.gc(); + } + + /** + * Clears out the cached images inside of this instance. + * @param runGarbageCollecter Whether to run the garbage collector after clearing all images. + * @param bypassExcludeKeys Whether to remove the images even if it's inside of the exclude list. + */ + public function clearImages(?runGarbageCollecter:Bool = true, ?bypassExcludeKeys:Bool = false):Void + { + for (image in cachedImageKeys) + { + removeImage(image, bypassExcludeKeys); + } + + FlxG.bitmap.reset(); // Flixel likes to cache all texts and transitions, nothing wrong with it but this is a graphic clear. + + if (runGarbageCollecter) + System.gc(); + } + + /** + * Removes and destroys an audio inside the audio cache. + * @param key The audio key you want to remove. + * @param bypassExcludeKeys Whether to remove the audio even if it's inside of the exclude list. + * @return Whether an object was successfully removed or not. + */ + public function removeAudio(key:String, ?bypassExcludeKeys:Bool = false):Bool + { + if (removeExcludeKeys.contains(key) && !bypassExcludeKeys) + return false; + + var wasRemoved:Bool = false; + + if (cachedAudio.get(key) != null) + { + cachedAudio.remove(key); + + wasRemoved = true; + } + + cachedAudioKeys.remove(key); + + return wasRemoved; + } + + /** + * Removes and destroys an image inside the audio cache. + * @param key The image key you want to remove. + * @param bypassExcludeKeys Whether to remove the image even if it's inside of the exclude list. + * @return Whether an object was successfully removed or not. + */ + public function removeImage(key:String, ?bypassExcludeKeys:Bool = false):Bool + { + if (removeExcludeKeys.contains(key) && !bypassExcludeKeys) + return false; + + var wasRemoved:Bool = false; + + if (cachedBitmapData.get(key) != null) + { + var bitmapData:BitmapData = cachedBitmapData.get(key); + bitmapData.dispose(); + cachedBitmapData.remove(key); + + wasRemoved = true; + } + + if (cachedFlxGraphic.get(key) != null) + { + var flxGraphic:FlxGraphic = cachedFlxGraphic.get(key); + flxGraphic.persist = false; + flxGraphic.destroyOnNoUse = true; + cachedFlxGraphic.remove(key); + + if (FlxG.bitmap.checkCache(key)) + FlxG.bitmap.remove(flxGraphic); + + if (flxGraphic != null) + flxGraphic.destroy(); + + wasRemoved = true; + } + + cachedImageKeys.remove(key); + + return wasRemoved; + } +} diff --git a/src/funkin/util/paths/PathsContent.hx b/src/funkin/util/paths/PathsContent.hx new file mode 100644 index 0000000..3e679d3 --- /dev/null +++ b/src/funkin/util/paths/PathsContent.hx @@ -0,0 +1,90 @@ +package funkin.util.paths; + +import flixel.graphics.FlxGraphic; +import flixel.graphics.frames.FlxAtlasFrames; +import flixel.graphics.frames.FlxFramesCollection; +import haxe.io.Bytes; +import openfl.Assets; +import openfl.display.BitmapData; +import openfl.media.Sound; + +/** + * A Paths class that helps returning objects or content based on stuff inside files. + */ +class PathsContent +{ + public var cache:PathsCache = null; + + /** + * Initializes all variables for use with this class. + */ + public function new() + { + cache = new PathsCache(); + } + + /** + * Returns an Sound instance with `key`'s audio file information inside of it. + * @param key The audio key to use for returning information. + * @return Sound instance with the key's information inside of it. + */ + public function audio(key:String):Sound + { + var assetKey:String = Paths.location.audio(key); + return cache.getAudio(assetKey); + } + + /** + * Returns an BitmapData instance with `key`'s image file information inside of it. + * @param key The image key to use for returning information. + * @return BitmapData instance with the image's information inside of it. + */ + public function imageBitmap(key:String):BitmapData + { + var assetKey:String = Paths.location.image(key); + return cache.getBitmapData(assetKey); + } + + /** + * Returns an FlxGraphic instance with `key`'s image file information inside of it. + * @param key The image key to use for returning information. + * @return FlxGraphic instance with the image's information inside of it. + */ + public function imageGraphic(key:String):FlxGraphic + { + var assetKey:String = Paths.location.image(key); + return cache.getFlxGraphic(assetKey); + } + + /** + * Returns an sparrow atlas information. + * @param key The image and xml key to use for returning information. + * @return Sparrow atlas into FlxFramesCollection with content inside of the image and xml. + */ + public function sparrowAtlas(key:String):FlxFramesCollection + { + return FlxAtlasFrames.fromSparrow(imageGraphic(key), xml(key)); + } + + /** + * Returns text from a file. + * @param key The text key to use for returning the text inside. + * @return A string with text from a file. + */ + public function text(key:String):String + { + var assetKey:String = Paths.location.get(key); + return Assets.getText(assetKey); + } + + /** + * Returns an xml object that is parsed from a file. + * @param key The xml key to use for returning the data inside. + * @return A Xml object parsed from text in a file. + */ + public function xml(key:String):Xml + { + var fileText:String = text(key + '.xml'); + return Xml.parse(fileText); + } +} diff --git a/src/funkin/util/paths/PathsLocation.hx b/src/funkin/util/paths/PathsLocation.hx new file mode 100644 index 0000000..01fd251 --- /dev/null +++ b/src/funkin/util/paths/PathsLocation.hx @@ -0,0 +1,86 @@ +package funkin.util.paths; + +import openfl.Assets; + +/** + * A Paths class that helps returning strings of paths. + */ +class PathsLocation +{ + /** + * This just ensures that the paths existence is here. + * + * There is no variables that need to be initialized however u will need to call this to be able to call functions from this class otherwise its null. + */ + public function new() + { + // + } + + /** + * Returns a path with the audio extenstion at the end. + * @param key The path to get. + * @return assets/`key`.`Paths.AUDIO_EXT` + */ + public function audio(key:String):String + { + return get(key + '.' + Paths.AUDIO_EXT); + } + + /** + * Returns a path with the image extenstion at the end. + * @param key The path to get. + * @return assets/`key`.`Paths.IMAGE_EXT` + */ + public function image(key:String):String + { + return get(key + '.' + Paths.IMAGE_EXT); + } + + /** + * Returns a path with the script extenstion at the end. + * @param key The path to get. + * @return assets/`key`.`Paths.SCRIPT_EXT` + */ + public function script(key:String):String + { + return get(key + '.' + Paths.SCRIPT_EXT); + } + + /** + * Returns a path that adds `.xml` to the end. + * @param key The path to get + * @return assets/`key`.xml + */ + public function xml(key:String):String + { + return get(key + '.xml'); + } + + /** + * Returns a path. Only returns assets/ + key since theres no mod support (yet.) + * @param key The path to get. + * @return assets/`key` + */ + public function get(key:String):String + { + return 'assets/' + key; + } + + /** + * Checks to see if a file exists inside the game. + * @param key The path to check the existence on. + * @return True if the file exists inside the game. + */ + public function exists(key:String):Bool + { + var path:String = get(key); + + if (Assets.exists(path)) + { + return true; + } + + return false; + } +}